NOTEBOOK - Stéphane Srsa

PROJET 10

Détectez des faux billets avec Python

billets

SOMMAIRE

  • 1 - Importation des librairies et des fonctions

  • 1.1 - Importation des librairies

  • 1.2 - Importation des fonctions

  • 1.3 - Chargement du fichier

  • 2 - Analyse préparatoire

  • 2.1 - Informations fichier

  • 2.2 - Analyses Univariées

  • 2.3 - Analyses Bivariées

  • 3 - Régression linéaire : Modèle prédictif pour remplacer les Nan dans 'margin_low'

  • 3.1 - Optimisation du Random_state en fonction du dataset

  • 3.2- Librairie Statsmodels pour régression linéaire simple et multiple

  • 3.3 - Librairie Sklearn pour régression linéaire multiple - Choix d'un prétraitement si nécessaire

  • 3.4 - Validation du nombre de variables prédictives à utiliser

  • 3.5 - Modèle retenu et Imputation des valeurs de 'margin_low' dans DF_NaN (sklearn)

  • 4 - Analyses et métriques sur les modèles avec ou sans Intercept

  • 4.1 - Comparaison des modèles avec et sans Intercept (Statsmodels)

  • 4.2 - Comparaison des Métriques

  • 4.3 - Validation et utilisation du modèle pour remplacer les valeurs manquantes

  • 5 - K-means

  • 5.1 - Visualisation via le modèle K-means des variables les plus prédictives - "margin_low" et "length"

  • 5.2 - Création du modèle K-means en utilisant train_test_split

  • 5.3 - PCA

  • 6 - Régression logistique

  • 6.1 - Régression Logistique Statsmodels et Sklearn

  • 6.2 - Choix de la fonction de Régression Logistique

  • 7 - Algorythme / Fonction de détection de faux billets

  • 7.1 - Choix de l'algorithme pour l'outil d'identification des billets

  • 7.2 - Création de fichiers Tests

  • 7.3 - Appel de la fonction et vérification des fichiers

  • 8 - Outil de définition du seuil d’acceptation

1 - Importation des librairies et des fonctions
1.1 - Importation des librairies
In [4]:
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.colors import ListedColormap
import seaborn as sns
from IPython.display import display, HTML
from tabulate import tabulate
import math

# Librairie Sklearn
from sklearn.linear_model import LinearRegression
from sklearn.metrics import mean_squared_error, mean_absolute_error, mean_absolute_percentage_error
from sklearn.model_selection import train_test_split
from sklearn.metrics import confusion_matrix, ConfusionMatrixDisplay
from sklearn.metrics import accuracy_score, precision_score, recall_score, f1_score, r2_score
from sklearn.metrics import davies_bouldin_score, adjusted_rand_score, calinski_harabasz_score
from sklearn.manifold import TSNE
from statsmodels.stats.outliers_influence import variance_inflation_factor
from sklearn.model_selection import learning_curve
from sklearn.metrics import make_scorer
from sklearn.metrics import silhouette_score
from sklearn import preprocessing

# Librairie StatModel
import statsmodels
from statsmodels.stats.diagnostic import het_breuschpagan
from statsmodels.tools.tools import add_constant
from statsmodels.discrete.discrete_model import Logit
import statsmodels.api as sm
import statsmodels.formula.api as smf

from scipy.stats import shapiro
from scipy.stats import kstest, norm

# Pour palier aux avertissements à l'utilisation de Seaborn (entre autres)
import warnings
warnings.filterwarnings("ignore")
1.2 - Importation des fonctions
In [2]:
from P10_Fonctions import *
1.3 - Chargement du fichier
In [3]:
DF_F = pd.read_csv('billets.csv', sep=';')
2 - Analyse préparatoire
2.1 - Informations fichier
In [4]:
infos_DF(DF_F)
# -------------------------------------  Heatmap des valeurs manquantes ------------------------------------
# Remplacer les NaN par True (pour les valeurs manquantes), sinon False
missing_values = DF_F.isnull()
# Heatmap des Nan
Tmessage('Heatmap des valeurs manquantes dans le DataFrame')
plt.figure(figsize=(12, 4))
sns.heatmap(missing_values, cmap='mako', cbar=False)
plt.show()

# -------------------------------------  Création d'une clé primaire ------------------------------------
Tmessage("Création d'une clé primaire")
# Créer une colonne 'ID' avec des identifiants uniques croissants
DF_F['ID'] = range(1, len(DF_F) + 1)
# Afficher le DataFrame avec la nouvelle colonne 'ID'
display(DF_F.head(2))
Text_message("Pour ce projet, une clé primaire n'est pas utile, je la supprime")
DF_F = DF_F.drop(columns='ID', axis=1)
______________________________________________________________________________________________________________________
Information du Fichier
is_genuine diagonal height_left height_right margin_low margin_up length
0 True 171.81 104.86 104.95 4.52 2.89 112.83
1 True 171.46 103.36 103.66 3.77 2.99 113.09
2 True 172.69 104.48 103.50 4.40 2.94 113.16
le fichier contient 1500 lignes et 7 colonnes

Statistiques descriptives du dataframe
Dtype Non-Null Count Valeurs unique moyennes medianes ecart_types min max Valeurs manquantes Valeurs manquantes%
is_genuine bool 1500 2 0.667 1.00 0.472 False True 0 0.00
diagonal float64 1500 159 171.958 171.96 0.305 171.04 173.01 0 0.00
height_left float64 1500 155 104.030 104.04 0.299 103.14 104.88 0 0.00
height_right float64 1500 170 103.920 103.92 0.326 102.82 104.95 0 0.00
margin_low float64 1463 285 4.486 4.31 0.664 2.98 6.9 37 2.47
margin_up float64 1500 123 3.151 3.14 0.232 2.27 3.91 0 0.00
length float64 1500 336 112.678 112.96 0.873 109.49 114.44 0 0.00
Vérification des doublons
is_genuine diagonal height_left height_right margin_low margin_up length
Absence de doublons dans le Dataframe
Heatmap des valeurs manquantes dans le DataFrame
No description has been provided for this image
Création d'une clé primaire
is_genuine diagonal height_left height_right margin_low margin_up length ID
0 True 171.81 104.86 104.95 4.52 2.89 112.83 1
1 True 171.46 103.36 103.66 3.77 2.99 113.09 2
Pour ce projet, une clé primaire n'est pas utile, je la supprime
2.2 - Analyses Univariées
In [6]:
# Distribution des variables
# On créé un Dataset sans les valeurs manquantes
DF_Complet = DF_F.dropna()
for i, column in enumerate(DF_Complet.columns, start=0):
    if column != 'is_genuine':  # Vérifier si la colonne est différente de 'is_genuine'
        # Graphique pour visualiser les outliers
        plt.figure(figsize=(12, 3))
        plt.subplot(1, 2, 1) 
        # Visualisation sous forme de graphique
        #sns.histplot(DF_F[column], kde=True)
        sns.histplot(DF_Complet[column], kde=True, color='teal', edgecolor='black', linewidth=1, line_kws={'color': 'red'})
        plt.title(f'Distribution de la colonne {column}', fontsize=14, color='firebrick', fontweight='bold')
        # Boîte à moustaches pour détecter les outliers
        plt.subplot(1, 2, 2)
        # Visualisation sous forme de boxplot
        sns.boxplot(x=DF_Complet[column], color='teal', 
                    palette = 'viridis', notch=True, whiskerprops={'linewidth': 1}, showmeans=True,
                    meanprops=dict(marker='p', markerfacecolor='white', markeredgecolor='black', markersize=8),
                    medianprops=dict(linestyle='-', linewidth=2, color='firebrick'),
                    flierprops=dict(marker='p', markerfacecolor='firebrick', markersize=5, markeredgecolor='black'),
                    boxprops=dict(linestyle='-', linewidth=1.5),
                    showfliers=True, width=0.4)
                   
        plt.title(f'Boxplot de la variable {column}', fontsize=14, color='firebrick', fontweight='bold')
        # Affichage valeurs médiane & moyenne:
        median_value = np.median(DF_Complet[column])
        moy_value = np.mean(DF_Complet[column])
        plt.axvline(x=median_value, ymin=0.5, ymax=0, color='firebrick', linestyle=':', linewidth=1)
        plt.axvline(x=moy_value, ymin=0.5, ymax=1, color='blue', linestyle=':', linewidth=1)
        plt.text(moy_value, -0.3, f'Moyenne: {moy_value:.2f}', color='blue', fontsize=12, ha='left')
        plt.text(median_value, 0.4, f'Médiane: {median_value:.2f}', color='firebrick', fontsize=12, ha='right')
        plt.tight_layout()
        plt.show()
        # Calcul de l'IQR
        Q1 = DF_F[column].quantile(0.25)
        Q3 = DF_F[column].quantile(0.75)
        IQR = Q3 - Q1
        # Détermination des bornes pour les outliers
        seuil_inf = Q1 - 1.5 * IQR
        seuil_sup = Q3 + 1.5 * IQR
        # Identification des outliers
        outliers = DF_Complet[(DF_Complet[column] < seuil_inf) | (DF_Complet[column] > seuil_sup)]
        # Affichage des outliers
        Text_message('{} : {} Outliers'.format(column, outliers.shape[0]))
        Tmessage('---------------------')
No description has been provided for this image
diagonal : 7 Outliers
---------------------
No description has been provided for this image
height_left : 6 Outliers
---------------------
No description has been provided for this image
height_right : 10 Outliers
---------------------
No description has been provided for this image
margin_low : 24 Outliers
---------------------
No description has been provided for this image
margin_up : 3 Outliers
---------------------
No description has been provided for this image
length : 3 Outliers
---------------------
2.3 - Analyses Bivariées
In [7]:
# Visualisation des données numériques
Tmessage("Visualisation des relations entre les variables")
sns.set(rc={'axes.facecolor':'black', 'figure.facecolor':'white'})
sns.pairplot(DF_F, hue="is_genuine", palette="viridis")
plt.show()
Text_message("Mise à part 'diagonal', 'Lenght' possède des corrélations +ou- fortes à vérifier avec les autres variables")
Tmessage("-------------------------------------------------------------------------",Color='blue')
message = "Distribution des variables en fonction de 'is_genuine'"
Tmessage(message) 
num_cols = len(DF_Complet.columns) - 1
num_rows = num_cols // 5
if num_cols % 5:
    num_rows += 1
sns.set(rc={'axes.facecolor':'white', 'figure.facecolor':'white'})
plt.figure(figsize=(10, 4 * num_rows))
for i, column in enumerate(DF_Complet.columns, start=0):
    if column != 'is_genuine':  # Vérifier si la colonne est différente de 'is_genuine'
        plt.subplot(num_rows, 6, i)
        sns.boxplot(x='is_genuine', y=column, data=DF_Complet, palette = "viridis", notch=True, showmeans=True,
                   meanprops=dict(marker='p', markerfacecolor='white', markeredgecolor='black', markersize=8),
                   medianprops=dict(linestyle='-', linewidth=2, color='orange'),
                   flierprops=dict(marker='p', markerfacecolor='firebrick', markersize=5, markeredgecolor='black'),
                   boxprops=dict(linestyle='-', linewidth=1.5), width=0.8)
        # Titre du graphique
        plt.title(column, y=1, 
          fontdict={'size': 12, 'weight': 'bold', 'style': 'italic', 'color': 'firebrick'})
        plt.ylabel('')
        plt.xlabel('')
        # Calcul des quantiles
        quantiles = np.quantile(DF_Complet.loc[DF_Complet['is_genuine'] == True, column], np.array([0.00, 0.25, 0.5, 0.75, 1]))
        # Tracé des lignes horizontales pour les quantiles
        for i, q in enumerate(quantiles):
            if i == 1 or i == 3:  # Sélectionne la deuxième valeur de quantiles
                plt.axhline(y=q, color='firebrick', linestyle='-', linewidth=2)
            else:
                plt.axhline(y=q, color='firebrick', linestyle=':', linewidth=1)
        plt.yticks(quantiles, fontsize=8)
plt.tight_layout()
plt.show()
Tmessage("-------------------------------------------------------------------------",Color='blue')
# Calcul pairwise-correlation
#matrix = DF_F.iloc[:,1:].corr(method='pearson')
matrix = DF_F.corr(method='pearson')
# Triangle de corrélations
mask1 = np.tril(np.ones_like(matrix, dtype=bool))
mask2 = np.eye(matrix.shape[0], dtype=bool)
mask = mask1 | mask2
# affichage
message = "Corrélation entre les indicateurs"
Tmessage(message)
plt.figure(figsize=(15, 4))
sns.heatmap(matrix, mask=~mask, annot=True, cmap='viridis')
plt.show()
Visualisation des relations entre les variables
No description has been provided for this image
Mise à part 'diagonal', 'Lenght' possède des corrélations +ou- fortes à vérifier avec les autres variables
-------------------------------------------------------------------------
Distribution des variables en fonction de 'is_genuine'
No description has been provided for this image
-------------------------------------------------------------------------
Corrélation entre les indicateurs
No description has been provided for this image

Pour vérifier 'is_genuine'

'margin_low', 'margin_up' et 'length' sont les plus prédictives

3 - Régression linéaire : Modèle prédictif pour remplacer les Nan dans 'margin_low'
3.1 - Optimisation du Random_state en fonction du dataset
In [8]:
variables = ['diagonal', 'height_left', 'height_right', 'margin_up', 'length']
reg = LinearRegression()
random_seeds = [n for n  in  range(0,200,1)]
# back to simple regression
X = DF_Complet[variables]
y = DF_Complet['margin_low']
scores = []
       
for seed in random_seeds:
    X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=seed)
        
    reg.fit(X_train, y_train)
    # Prediction sur le test set
    y_pred_test = reg.predict(X_test)

    scores.append({'seed': seed, 'RMSE': np.round(mean_squared_error(y_test, y_pred_test),4),
                'MAPE' : np.round(mean_absolute_percentage_error(y_test, y_pred_test),4),
                'R^2' : np.round(reg.score(X_train, y_train), 3)})
    
scores = pd.DataFrame(scores)
# On conserve la valeur du random_state pour laquelle MAPE est au minimum
Text_message('Valeur du random_state retenue =  {}'.format(round(scores.loc[scores['MAPE'].idxmin()]['seed']),0))
random_state = int(round((scores.loc[scores['MAPE'].idxmin()]['seed']),0))
Valeur du random_state retenue = 61

On fixe random_state à 61

Cette valeur n'est valable que dans le cadre de notre dataset

3.2 - Librairie Statsmodels pour régression linéaire simple et multiple
In [9]:
# Remplacement du bool par 0 ou 1
DF_F["is_genuine"].replace([True, False], [1,0], inplace=True)

# On créé un Dataset avec les individus qui ont un marhin_low = NaN
DF_NaN = DF_F.loc[DF_F["margin_low"].isna()]

# On créé un Dataset sans les valeurs manquantes
DF_Complet = DF_F.dropna()

régression linéaire simplet

Prenons la variable la plus corrélée 'Length' avec 'margin_low'
In [10]:
DF_RLS = DF_Complet.copy()
# Choisissez vos variables indépendantes et dépendantes
X = DF_RLS["length"]  # Choisissez vos variables indépendantes
y = DF_RLS["margin_low"]  # Variable dépendante (continue)

# Ajoutez une colonne constante à X pour l'intercept
X_with_const = sm.add_constant(X)
# Divisez vos données en ensembles d'entraînement et de test
X_train, X_test, y_train, y_test = train_test_split(X_with_const, y, test_size=0.2, random_state=random_state)

# Créez le modèle de régression linéaire
model = sm.OLS(y_train, X_train)
# Ajustez le modèle aux données
result = model.fit()
# Affichez les résultats
print(result.summary())

# Faites des prédictions sur les données de test
y_pred = result.predict(X_test)
# Calcul du RMSE
rmse = np.sqrt(mean_squared_error(y_test, y_pred))
# Calcul du MAE
mae = mean_absolute_error(y_test, y_pred)
# Calcul du MAPE
mape = np.mean(np.abs((y_test - y_pred) / y_test)) * 100
# Affichage des résultats
Text_message("RMSE: {}".format(rmse.round(4)))
Text_message("MAE: {}".format( mae.round(4)))
Text_message("MAPE: {} %".format(mape.round(4)))
                            OLS Regression Results                            
==============================================================================
Dep. Variable:             margin_low   R-squared:                       0.440
Model:                            OLS   Adj. R-squared:                  0.439
Method:                 Least Squares   F-statistic:                     916.1
Date:                Thu, 25 Jul 2024   Prob (F-statistic):          4.83e-149
Time:                        19:50:22   Log-Likelihood:                -855.58
No. Observations:                1170   AIC:                             1715.
Df Residuals:                    1168   BIC:                             1725.
Df Model:                           1                                         
Covariance Type:            nonrobust                                         
==============================================================================
                 coef    std err          t      P>|t|      [0.025      0.975]
------------------------------------------------------------------------------
const         61.3339      1.878     32.652      0.000      57.648      65.019
length        -0.5046      0.017    -30.267      0.000      -0.537      -0.472
==============================================================================
Omnibus:                       64.168   Durbin-Watson:                   1.970
Prob(Omnibus):                  0.000   Jarque-Bera (JB):               82.522
Skew:                           0.514   Prob(JB):                     1.20e-18
Kurtosis:                       3.799   Cond. No.                     1.44e+04
==============================================================================

Notes:
[1] Standard Errors assume that the covariance matrix of the errors is correctly specified.
[2] The condition number is large, 1.44e+04. This might indicate that there are
strong multicollinearity or other numerical problems.
RMSE: 0.4608
MAE: 0.3435
MAPE: 7.4661 %

régression linéaire multiple

In [11]:
# Choisissez vos variables indépendantes et dépendantes
X = DF_Complet.drop(["is_genuine", "margin_low"], axis=1)  # Choisissez vos variables indépendantes
y = DF_Complet["margin_low"]  # Variable dépendante (continue)

# Ajoutez une colonne constante à X pour l'intercept
X_with_const = sm.add_constant(X)
# Divisez vos données en ensembles d'entraînement et de test
X_train, X_test, y_train, y_test = train_test_split(X_with_const, y, test_size=0.2, random_state=random_state)

# Créez le modèle de régression linéaire
model = sm.OLS(y_train, X_train)
# Ajustez le modèle aux données
result = model.fit()
# Affichez les résultats
print(result.summary())

# Faites des prédictions sur les données de test
y_pred = result.predict(X_test)
# Calcul du RMSE
rmse = np.sqrt(mean_squared_error(y_test, y_pred))
# Calcul du MAE
mae = mean_absolute_error(y_test, y_pred)
# Calcul du MAPE
mape = np.mean(np.abs((y_test - y_pred) / y_test)) * 100
# Affichage des résultats
Text_message("RMSE: {}".format(rmse.round(4)))
Text_message("MAE: {}".format( mae.round(4)))
Text_message("MAPE: {} %".format(mape.round(4)))
                            OLS Regression Results                            
==============================================================================
Dep. Variable:             margin_low   R-squared:                       0.471
Model:                            OLS   Adj. R-squared:                  0.469
Method:                 Least Squares   F-statistic:                     207.7
Date:                Thu, 25 Jul 2024   Prob (F-statistic):          2.36e-158
Time:                        19:50:25   Log-Likelihood:                -821.31
No. Observations:                1170   AIC:                             1655.
Df Residuals:                    1164   BIC:                             1685.
Df Model:                           5                                         
Covariance Type:            nonrobust                                         
================================================================================
                   coef    std err          t      P>|t|      [0.025      0.975]
--------------------------------------------------------------------------------
const           22.1433     10.994      2.014      0.044       0.572      43.714
diagonal        -0.1017      0.046     -2.191      0.029      -0.193      -0.011
height_left      0.1702      0.051      3.322      0.001       0.070       0.271
height_right     0.2593      0.049      5.338      0.000       0.164       0.355
margin_up        0.2827      0.074      3.828      0.000       0.138       0.428
length          -0.4058      0.020    -19.857      0.000      -0.446      -0.366
==============================================================================
Omnibus:                       43.110   Durbin-Watson:                   1.984
Prob(Omnibus):                  0.000   Jarque-Bera (JB):               50.661
Skew:                           0.420   Prob(JB):                     9.98e-12
Kurtosis:                       3.577   Cond. No.                     1.94e+05
==============================================================================

Notes:
[1] Standard Errors assume that the covariance matrix of the errors is correctly specified.
[2] The condition number is large, 1.94e+05. This might indicate that there are
strong multicollinearity or other numerical problems.
RMSE: 0.4451
MAE: 0.3251
MAPE: 7.0297 %

Suppression des variables 'diagonal' et 'height_left'¶

In [12]:
# Choisissez vos variables indépendantes et dépendantes
X = DF_Complet.drop(["is_genuine", "margin_low", "diagonal", "height_left"], axis=1)  # Choisissez vos variables indépendantes
y = DF_Complet["margin_low"]  # Variable dépendante (continue)

# Ajoutez une colonne constante à X pour l'intercept
X_with_const = sm.add_constant(X)
# Divisez vos données en ensembles d'entraînement et de test
X_train, X_test, y_train, y_test = train_test_split(X_with_const, y, test_size=0.2, random_state=random_state)

# Créez le modèle de régression linéaire
model = sm.OLS(y_train, X_train)
# Ajustez le modèle aux données
result = model.fit()
# Affichez les résultats
print(result.summary())

# Faites des prédictions sur les données de test
y_pred = result.predict(X_test)
# Calcul du RMSE
rmse = np.sqrt(mean_squared_error(y_test, y_pred))
# Calcul du MAE
mae = mean_absolute_error(y_test, y_pred)
# Calcul du MAPE
mape = np.mean(np.abs((y_test - y_pred) / y_test)) * 100
# Affichage des résultats
Text_message("RMSE: {}".format(rmse.round(4)))
Text_message("MAE: {}".format( mae.round(4)))
Text_message("MAPE: {} %".format(mape.round(4)))
                            OLS Regression Results                            
==============================================================================
Dep. Variable:             margin_low   R-squared:                       0.464
Model:                            OLS   Adj. R-squared:                  0.463
Method:                 Least Squares   F-statistic:                     337.1
Date:                Thu, 25 Jul 2024   Prob (F-statistic):          1.38e-157
Time:                        19:50:26   Log-Likelihood:                -828.97
No. Observations:                1170   AIC:                             1666.
Df Residuals:                    1166   BIC:                             1686.
Df Model:                           3                                         
Covariance Type:            nonrobust                                         
================================================================================
                   coef    std err          t      P>|t|      [0.025      0.975]
--------------------------------------------------------------------------------
const           22.8377      6.157      3.709      0.000      10.758      34.917
height_right     0.2718      0.049      5.590      0.000       0.176       0.367
margin_up        0.3075      0.074      4.155      0.000       0.162       0.453
length          -0.4222      0.020    -20.999      0.000      -0.462      -0.383
==============================================================================
Omnibus:                       49.349   Durbin-Watson:                   1.978
Prob(Omnibus):                  0.000   Jarque-Bera (JB):               59.018
Skew:                           0.454   Prob(JB):                     1.53e-13
Kurtosis:                       3.622   Cond. No.                     6.56e+04
==============================================================================

Notes:
[1] Standard Errors assume that the covariance matrix of the errors is correctly specified.
[2] The condition number is large, 6.56e+04. This might indicate that there are
strong multicollinearity or other numerical problems.
RMSE: 0.4512
MAE: 0.3293
MAPE: 7.1151 %
In [13]:
# Créez un graphique de dispersion des valeurs observées vs prédites
plt.figure(figsize=(12,3))
Tmessage("Régression Linéaire - Valeurs Observées vs Prédites (Set de Tests)")
sns.scatterplot(x=y_test, y=y_pred, color='teal', alpha=0.5)
sns.lineplot(x=y_test, y=y_test, color='red', label="Droite de Régression Linéaire")
plt.xlabel("Valeurs Observées")
plt.ylabel("Valeurs Prédites")
plt.show()
Régression Linéaire - Valeurs Observées vs Prédites (Set de Tests)
No description has been provided for this image

Colinéarité des variables

In [14]:
# Calcul des VIF pour chaque variable indépendante
vif_data = pd.DataFrame()
vif_data["Variable"] = X_with_const.columns
vif_data["VIF"] = [variance_inflation_factor(X_with_const.values, i) for i in range(X_with_const.shape[1])]

# Affichage des résultats
vif_data = vif_data.drop(index=0).reset_index(drop=True)
display(vif_data)
Tmessage("Coefficients inférieurs à 10 (Avec Intercept et Sans Intercept(data scalées))", 
         Color='black', Align='left', Size='12')
Tmessage("Il n'y a donc pas de problème de colinéarité", Color='firebrick', Align='left', Size='12')
Variable VIF
0 height_right 1.213794
1 margin_up 1.394010
2 length 1.509435
Coefficients inférieurs à 10 (Avec Intercept et Sans Intercept(data scalées))
Il n'y a donc pas de problème de colinéarité

VIF = 1 : Aucune colinéarité. Les variables indépendantes ne sont pas corrélées les unes avec les autres.

1 < VIF < 5 : Colineárité modérée. Bien que certaine colinéarité puisse être présente, elle est généralement considérée comme acceptable dans cette plage.

VIF ≥ 5 : Colineárité élevée. Les variables indépendantes sont fortement corrélées, ce qui peut entraîner une instabilité dans les estimations des coefficients.

VIF ≥ 10 : Colineárité très élevée. Ceci indique une forte colinéarité, et il est généralement recommandé de prendre des mesures correctives, telles que l'exclusion de certaines variables.

Homoscédasticité

In [15]:
from statsmodels.stats.diagnostic import het_breuschpagan

# Test de Breusch-Pagan
test_result_bp = het_breuschpagan(result.resid, X_train)
Tmessage("Test de Breusch-Pagan:")
print("Statistique de test:", test_result_bp[0])
print("p-valeur:", test_result_bp[1])
print("L'effet de l'hétéroscédasticité est-il significatif ?", test_result_bp[1] < 0.05)
Tmessage("la variance des résidus n'est pas constante", Align='left', Color = 'blue')

# Calculer les résidus standardisés
residuals = result.resid
# Créer un graphique de dispersion
plt.figure(figsize=(10, 4))
Tmessage("Homoscédasticité : Dispersion des Résidus")
sns.scatterplot(x=X_train.index, y=residuals, color='teal', alpha=0.7)
plt.ylabel("Résidus")
plt.axhline(0, color='red', linestyle='--', linewidth=2, label='Ligne Zéro')

# Ajouter un peu de style avec une ligne en pointillé rouge représentant la ligne zéro
plt.legend()
plt.show()
Test de Breusch-Pagan:
Statistique de test: 68.51546882848272
p-valeur: 8.873495179862817e-15
L'effet de l'hétéroscédasticité est-il significatif ? True
la variance des résidus n'est pas constante
Homoscédasticité : Dispersion des Résidus
No description has been provided for this image

Statistique de test :

La statistique de test, généralement notée chi-carré (χ²), mesure à quel point les erreurs de variance sont homogènes. Plus la statistique de test est élevée, plus l'hétéroscédasticité est probable.

la p-valeur est < 0.05 :

Il y a des preuves significatives d'hétéroscédasticité dans les résidus du modèle. Nous devrions envisager des approches pour corriger l'hétéroscédasticité, telles que l'utilisation de méthodes de régression robustes.

Normalité des résidus

In [16]:
# Calculer les résidus
residuals = y_test - y_pred

plt.figure(figsize=(12,3))
Tmessage("Histogramme des résidus")
sns.distplot(residuals, bins='auto', color = 'teal')
plt.xlabel('Résidus')
plt.ylabel('Fréquence')
plt.show()

plt.figure(figsize=(12,3))
Tmessage("Graphique quantile-quantile des résidus")
# Tracer le graphique quantile-quantile (Q-Q plot)
stats.probplot(residuals, dist="norm", plot=plt)
plt.xlabel("Quantiles théoriques")
plt.ylabel("Quantiles observés")
plt.show()

# Test de Shapiro-Wilk: 
Tmessage("Test de Shapiro-Wilk", Color='teal',Size= 14)
stat, p_value = shapiro(result.resid)
Tmessage("Statistic: {}".format(stat), Align='left', Color='Black',Size= 12)
Tmessage("p-value: {}".format(p_value), Align='left', Color='Black',Size= 12)
if p_value > 0.05:
    Tmessage("Les résidus suivent une distribution normale.", Align='left', Color='firebrick',Size= 12)
else:
    Tmessage("Les résidus ne suivent pas une distribution normale.", Align='left', Color='firebrick',Size= 12)

# Test de Kolmogorov-Smirnov:
Tmessage("Test de Kolmogorov-Smirnov", Color='teal',Size= 14)
stat, p_value = kstest(result.resid, norm.cdf)
Tmessage("Statistic: {}".format(stat), Align='left', Color='Black',Size= 12)
Tmessage("p-value: {}".format(p_value), Align='left', Color='Black',Size= 12)
if p_value > 0.05:
    Tmessage("Les résidus suivent une distribution normale.", Align='left', Color='firebrick',Size= 12)
else:
    Tmessage("Les résidus ne suivent pas une distribution normale.", Align='left', Color='firebrick',Size= 12)
Histogramme des résidus
No description has been provided for this image
Graphique quantile-quantile des résidus
No description has been provided for this image
Test de Shapiro-Wilk
Statistic: 0.9879477331205726
p-value: 3.126863514043968e-08
Les résidus ne suivent pas une distribution normale.
Test de Kolmogorov-Smirnov
Statistic: 0.18878741830830323
p-value: 5.501821618615567e-37
Les résidus ne suivent pas une distribution normale.
In [ ]:
 
3.3 - Librairie Sklearn pour régression linéaire multiple - Choix d'un prétraitement si nécessaire
In [17]:
from sklearn.preprocessing import MinMaxScaler
from sklearn import preprocessing
from sklearn.preprocessing import PowerTransformer
from sklearn.preprocessing import Normalizer

regressions = {
    'margin_low ~ diagonal + height_left + height_right + margin_up + length'
    : ['diagonal','height_left', 'height_right', 'margin_up', 'length']}

# boucle sur les régressions
for title, variables in regressions.items(): 
    Tmessage('----------- Choix du pré-traitement sur :--------------',Color='blue')
    Tmessage(title)
    # Instancier et appliquer le Scaler 
    scal = [Normalizer(), PowerTransformer(method='yeo-johnson'), preprocessing.StandardScaler(), MinMaxScaler(), 
            'Transformation logarithmique', 'Sans traitement']
    for scl in scal:
        if scl == 'Transformation logarithmique':
            DF_clean = DF_Complet.mask(DF_Complet[variables] <= 0, 0.1)
            data_array = np.log1p(DF_clean)
            # Conversion en dataframe 
            df_scaled = pd.DataFrame(data_array,  columns = variables)
        elif scl == 'Sans traitement':  
            df_scaled = DF_Complet.copy()
        else:
            scaler = scl 
            data_array = scaler.fit_transform(DF_Complet[variables])
            # Conversion en dataframe 
            df_scaled = pd.DataFrame(data_array,  columns = variables)
        
    # Affichage du prétraitement:
        message = f'  {scl}'
        Tmessage(message, Color='blue', Align='left', Style='none', Size='12', Police='arial')

    # les variables prédictives
        X = df_scaled[variables]
    # la variable cible, le poids
        y = DF_Complet['margin_low']
    # Train test split
        X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.20, random_state=random_state)
    # entrainer le modele
        reg = LinearRegression()
        reg.fit(X_train, y_train)
    # Prediction sur le test set
        y_pred_test = reg.predict(X_test)
    
    # Scores
        a = np.round(reg.score(X_train, y_train), 3)
        b = np.round(mean_squared_error(y_test, y_pred_test),4)
        c = np.round(mean_absolute_error(y_test, y_pred_test),4)
        d = np.round(mean_absolute_percentage_error(y_test, y_pred_test),4)
        Text_message("R^2 : {} --- RMSE: {} --- MAE : {} --- MAPE: {}".format(a,b,c,d))
----------- Choix du pré-traitement sur :--------------
margin_low ~ diagonal + height_left + height_right + margin_up + length
Normalizer()
R^2 : 0.47 --- RMSE: 0.1989 --- MAE : 0.325 --- MAPE: 0.0702
PowerTransformer()
R^2 : 0.452 --- RMSE: 0.2062 --- MAE : 0.3414 --- MAPE: 0.0742
StandardScaler()
R^2 : 0.471 --- RMSE: 0.1981 --- MAE : 0.3251 --- MAPE: 0.0703
MinMaxScaler()
R^2 : 0.471 --- RMSE: 0.1981 --- MAE : 0.3251 --- MAPE: 0.0703
Transformation logarithmique
R^2 : 0.471 --- RMSE: 0.1981 --- MAE : 0.3248 --- MAPE: 0.0702
Sans traitement
R^2 : 0.471 --- RMSE: 0.1981 --- MAE : 0.3251 --- MAPE: 0.0703

Aucun pré-traitement ne sort du lot

On conserve le DF sans traitement

3.4 - Validation du nombre de variables prédictives à utiliser
In [18]:
regressions = {
    'margin_low ~ diagonal + height_left + height_right + margin_up + length': ['diagonal','height_left', 'height_right', 'margin_up', 'length'],
    'margin_low ~ height_left + height_right + margin_up + length': ['height_left', 'height_right', 'margin_up', 'length'],
    'margin_low ~ height_right + margin_up + length': ['height_right', 'margin_up', 'length'],
    'margin_low ~ margin_up + length': ['margin_up', 'length'], 'margin_low ~ length': ['length']
}
reg = LinearRegression()

# boucle sur les régressions
for title, variables in regressions.items():
    # les variables prédictives
    scaler = preprocessing.StandardScaler()
    data_array = scaler.fit_transform(DF_Complet[variables])
    df_scaled = pd.DataFrame(data_array,  columns = variables)
    X = DF_Complet[variables]
    # la variable cible, le poids
    y = DF_Complet.margin_low
    
    # Train test split
    X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.20, random_state=random_state)

    # entrainer le modele
    reg.fit(X_train, y_train)
    # Prediction sur le test set
    y_pred_test = reg.predict(X_test)
    
    # Scores
    Tmessage("---------------------------Variables prédictives pour 'margin_low' -----------------------",Color='blue')
    Tmessage(title)
    a = np.round(reg.score(X_train, y_train), 3)
    b = np.round(mean_squared_error(y_test, y_pred_test),4)
    c = np.round(mean_absolute_error(y_test, y_pred_test),4)
    d = np.round(mean_absolute_percentage_error(y_test, y_pred_test),5)
    Text_message("R^2 : {} --- RMSE: {} --- MAE : {} --- MAPE: {} %".format(a,b,c,d*100))
    mape = (1 / len(y_test)) * sum(abs((y_test - y_pred_test) / y_test) * 100)
    #print("MAPE:", mape)
---------------------------Variables prédictives pour 'margin_low' -----------------------
margin_low ~ diagonal + height_left + height_right + margin_up + length
R^2 : 0.471 --- RMSE: 0.1981 --- MAE : 0.3251 --- MAPE: 7.03 %
---------------------------Variables prédictives pour 'margin_low' -----------------------
margin_low ~ height_left + height_right + margin_up + length
R^2 : 0.469 --- RMSE: 0.1998 --- MAE : 0.3257 --- MAPE: 7.037 %
---------------------------Variables prédictives pour 'margin_low' -----------------------
margin_low ~ height_right + margin_up + length
R^2 : 0.464 --- RMSE: 0.2036 --- MAE : 0.3293 --- MAPE: 7.115 %
---------------------------Variables prédictives pour 'margin_low' -----------------------
margin_low ~ margin_up + length
R^2 : 0.45 --- RMSE: 0.2101 --- MAE : 0.3355 --- MAPE: 7.271 %
---------------------------Variables prédictives pour 'margin_low' -----------------------
margin_low ~ length
R^2 : 0.44 --- RMSE: 0.2123 --- MAE : 0.3435 --- MAPE: 7.466 %

margin_low ~ 'toutes variables' a le MAPE le plus bas

Nous avons vu que 'diagonal' et 'height_left' ne sont que peu prédictives

----

margin_low ~ height_right + margin_up + length est validé avec MAPE = 7.115 %

3.5 - Modèle comprenant l'intercept (sklearn)
In [19]:
regressions = {'margin_low ~ height_right + margin_up + length': ['height_right', 'margin_up', 'length']}
reg = LinearRegression()
# boucle sur les régressions
for title, variables in regressions.items():
    # les variables prédictives
    X = DF_Complet[variables]
    # la variable cible, le poids
    y = DF_Complet.margin_low
    
    # Train test split
    X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.20, random_state=random_state)

    # entrainer le modele
    reg.fit(X_train, y_train)
    # Prediction sur le test set
    y_pred_test = reg.predict(X_test)
    
    # Scores
    Tmessage("--------------------------- Variables prédictives ------------------------",Color='blue')
    Tmessage(title)
    a = np.round(reg.score(X_train, y_train), 3)
    b = np.round(mean_squared_error(y_test, y_pred_test),4)
    c = np.round(mean_absolute_error(y_test, y_pred_test),4)
    d = np.round(mean_absolute_percentage_error(y_test, y_pred_test),4)
    Text_message("R^2 : {} --- RMSE: {} --- MAE : {} --- MAPE: {}".format(a,b,c,d))
    
    # Créez un DataFrame contenant les caractéristiques pour prédiction
    data_to_predict = X_test

    # Utilisez le modèle pour prédire les valeurs pour les données manquantes dans 'margin_low'
    prediction_array = reg.predict(data_to_predict)

    merged_df = pd.merge(X_test, y_test, left_index=True, right_index=True)

    # Remplacez les valeurs NaN dans 'margin_low' par les valeurs prédites
    merged_df['margin_low_Predict'] = prediction_array
    merged_df['% erreur'] = round(abs(100 - (merged_df['margin_low_Predict']*100 / merged_df['margin_low'])),2)
    # Affichage
    Color='blue'
    Align='left'
    Style='none'
    Size='12'
    Police='arial'
    Tmessage("Vérification des résultats sur l'échantillon Test",Color, Align, Style, Size)
    display(merged_df.head(3))
    Text_message("Vérification erreur moyenne : {}  %".format(merged_df['% erreur'].mean().round(3)))
    
    # Créez un DataFrame contenant les caractéristiques pour prédiction
    data_to_predict = DF_NaN[variables]

    # Utilisez le modèle pour prédire les valeurs pour les données manquantes dans 'margin_low'
    prediction_array = reg.predict(data_to_predict)

    # Remplacez les valeurs NaN dans 'margin_low' par les valeurs prédites
    DF_NaN['margin_low'] = prediction_array.round(3)
    # Affichage
    Color='blue'
    Align='left'
    Style='none'
    Size='12'
    Police='arial'
    Tmessage("Imputation des 'margin-low' dans le DF_NaN",Color, Align, Style, Size)
    display(DF_NaN.head(3))

# ________________________________________ Graphique _____________________________
Tmessage("--------------------------- Graphiques d'interprétation ------------------------",Color='blue')
DFC = DF_Complet.copy()
DFN = DF_NaN.copy()
# Création de la figure et des sous-graphiques
fig, axs = plt.subplots(1, 3, figsize=(12, 4))

# Tracé des graphiques dans chaque sous-graphique
plot_regression(DFC, DFN, 'length', "'margin-low' en fonction de 'length'", axs[0])
plot_regression(DFC, DFN, 'height_right', "'margin-low' en fonction de 'height_right'", axs[1])
plot_regression(DFC, DFN, 'margin_up', "'margin-low' en fonction de 'margin_up'", axs[2])

# Rétablir les valeurs originales pour éviter les modifications permanentes dans les DataFrames
DFC['is_genuine'] = DFC['is_genuine'].replace({'Faux': 0, 'Vrai': 1})
DFN['is_genuine'] = DFN['is_genuine'].replace({'Faux': 0, 'Vrai': 1})

# Récupérer les positions des axes
pos1 = axs[0].get_position()
pos2 = axs[1].get_position()

# Créer une légende commune
fig.legend(labels=['DF_Complet','Faux billets', 'Vrais billets', 'Régression linéaire',
                   'Interval de confiance', 'DF_NaN', 'Prédict Vrais billets', 'Prédict Faux billets'],
           loc='upper center', bbox_to_anchor=((pos1.x0 + pos2.x1) / 1.5, pos1.y1-0.9), ncol=4)
plt.show()
--------------------------- Variables prédictives ------------------------
margin_low ~ height_right + margin_up + length
R^2 : 0.464 --- RMSE: 0.2036 --- MAE : 0.3293 --- MAPE: 0.0712
Vérification des résultats sur l'échantillon Test
height_right margin_up length margin_low margin_low_Predict % erreur
1301 104.19 3.25 111.99 5.72 4.867881 14.90
1028 103.93 3.49 111.34 5.38 5.145473 4.36
281 104.21 3.07 113.01 4.18 4.387276 4.96
Vérification erreur moyenne : 7.115 %
Imputation des 'margin-low' dans le DF_NaN
is_genuine diagonal height_left height_right margin_low margin_up length
72 1 171.94 103.89 103.45 4.329 3.25 112.79
99 1 171.93 104.07 104.18 4.371 3.14 113.08
151 1 172.07 103.80 104.38 4.452 3.02 112.93
--------------------------- Graphiques d'interprétation ------------------------
No description has been provided for this image
4 - Analyses et métriques sur les modèles avec ou sans Intercept
4.1 - Comparaison des modèles avec et sans Intercept (Statsmodels)
Création du modèle de régression avec Statsmodels: forcer l'intercept à zéro peut être pertinent.¶
Théoriquement, la droite de régression passe par l'origine du graphique (0,0).¶
Dans notre cas, on suppose que lorsque toutes les variables indépendantes (prédictives) sont nulles, la variable dépendante (margin_low) est également nulle.¶
In [20]:
df =DF_Complet.copy()
test_size = 0.2
# Séparer les données en ensemble d'entraînement et ensemble de test
train_df, test_df = train_test_split(df, test_size=test_size, random_state=random_state)

# Création du modèle de régression linéaire
model = smf.ols(formula='margin_low ~ height_right + margin_up + length', data=train_df)
# Ajuster le modèle aux données d'entraînement
results_AvecIntercept = model.fit()
# Effectuer des prédictions sur l'ensemble de test
predictions_AvecIntercept = results_AvecIntercept.predict(X_test)

# Calculer MAE
mae = mean_absolute_error(y_test, predictions_AvecIntercept)
# Calculer RMSE
rmse = np.sqrt(mean_squared_error(y_test, predictions_AvecIntercept))
# Calculer MAPE
def mean_absolute_percentage_error(y_true, y_pred): 
    y_true, y_pred = np.array(y_true), np.array(y_pred)
    return np.mean(np.abs((y_true - y_pred) / y_true)) * 100

mape = mean_absolute_percentage_error(y_test, predictions_AvecIntercept)
# Calculer le coefficient de détermination (R²)
r_squared = r2_score(y_test, predictions_AvecIntercept)
Tmessage('Résultats avec intercept', Align='left')
Text_message(f"R-squared (R²): {r_squared:.4f}")
Text_message(f"MAE: {mae:.5f}")
Text_message(f"RMSE: {rmse:.5f}")
Text_message(f"MAPE: {mape:.5f}%")


df =DF_Complet.copy()
# Séparation des données
train_df, test_df = train_test_split(df, test_size=test_size, random_state=random_state)

# Création du modèle de régression linéaire
model = smf.ols(formula='margin_low ~ height_right + margin_up + length - 1', data=train_df)
# On fit
results_SansIntercept = model.fit() 
# Prédictions sur Test
predictions_SansIntercept = results_SansIntercept.predict(X_test)
# Affichage
#display(results.summary())

# Calculer MAE
mae = mean_absolute_error(y_test, predictions_SansIntercept)
# Calculer RMSE
rmse = np.sqrt(mean_squared_error(y_test, predictions_SansIntercept))
# Calculer MAPE
def mean_absolute_percentage_error(y_true, y_pred): 
    y_true, y_pred = np.array(y_true), np.array(y_pred)
    return np.mean(np.abs((y_true - y_pred) / y_true)) * 100
mape = mean_absolute_percentage_error(y_test, predictions_SansIntercept)
# Calcule du coef de détermination (R²)
r_squared = r2_score(y_test, predictions_SansIntercept)
Tmessage('Résultats sans intercept', Align='left')
Text_message(f"R-squared (R²): {r_squared:.4f}")
Text_message(f"MAE: {mae:.5f}")
Text_message(f"RMSE: {rmse:.5f}")
Text_message(f"MAPE: {mape:.5f}%")
Résultats avec intercept
R-squared (R²): 0.4878
MAE: 0.32930
RMSE: 0.45122
MAPE: 7.11513%
Résultats sans intercept
R-squared (R²): 0.4796
MAE: 0.33046
RMSE: 0.45481
MAPE: 7.12140%
4.2 - Comparaison des Métriques

AIC (Akaike Information Criterion) & BIC (Bayesian Information Criterion)

In [21]:
# Calcul de l'AIC et du BIC Avec Intercept
aic = round(results_AvecIntercept.aic,3)
bic = round(results_AvecIntercept.bic,3)
Tmessage("Avec Intercept:",Align='left')
Tmessage("AIC : {} / BIC : {}".format(aic,bic),Color ='teal', Align='left', Style='italic', Size='12', Police='arial')

# Calcul de l'AIC et du BIC Sans Intercept
aic = round(results_SansIntercept.aic,3)
bic = round(results_SansIntercept.bic,3)
Tmessage("Sans Intercept:",Align='left', Style='italic', Size='14', Police='arial')
Tmessage("AIC : {} / BIC : {}".format(aic,bic),Color ='teal', Align='left', Style='italic', Size='12', Police='arial')
Tmessage("Les différences entre les modèles ne sont pas suffisantes pour déterminer le meilleur compromis entre ajustement et complexité",
         Color ='blue', Align='center', Style='italic', Size='12', Police='arial')
Avec Intercept:
AIC : 1665.931 / BIC : 1686.19
Sans Intercept:
AIC : 1677.658 / BIC : 1692.852
Les différences entre les modèles ne sont pas suffisantes pour déterminer le meilleur compromis entre ajustement et complexité

Indépendance des erreurs

In [22]:
# Récupération des valeurs prédites et des résidus sans intercept
predicted_values = results_SansIntercept.fittedvalues
residuals = results_SansIntercept.resid
# Récupération des valeurs prédites et des résidus avec intercept
predicted_values1 = results_AvecIntercept.fittedvalues
residuals1 = results_AvecIntercept.resid


# Création d'un DataFrame pour le graphique des résidus
residuals_df = pd.DataFrame({'Valeurs prédites': predicted_values, 'Résidus': residuals})
residuals_df1 = pd.DataFrame({'Valeurs prédites': predicted_values1, 'Résidus': residuals1})

# Tracé du graphique des résidus
sns.set(rc={'axes.facecolor': 'white', 'figure.facecolor': 'white'})
plt.figure(figsize=(12,4))
sns.residplot(data=residuals_df, x='Valeurs prédites', y='Résidus', color='teal', scatter_kws={'alpha': 0.8}, lowess=True, 
              line_kws={'color': 'firebrick', 'linewidth': 6})
sns.residplot(data=residuals_df1, x='Valeurs prédites', y='Résidus', color='orange', scatter_kws={'alpha': 0.5}, lowess=True, 
              line_kws={'color': 'blue', 'linewidth': 2})

plt.xlabel('Valeurs prédites')
plt.ylabel('Résidus')
plt.title('Graphique des résidus pour une régression linéaire multiple', y=1, 
          fontdict={'size': 12, 'weight': 'bold', 'style': 'italic', 'color': 'firebrick'})
# Ajout de légendes factices pour le graphique
plt.scatter([], [], color='teal', label='Sans intercept')  # Élément pour les points dispersés
plt.plot([], [], color='firebrick', linewidth=2, label='Régression lissée')  # Élément pour la ligne de régression lissée
plt.scatter([], [], color='orange', label='Avec intercept')  # Élément pour les points dispersés
plt.plot([], [], color='blue', linewidth=2, label='Régression lissée')  # Élément pour la ligne de régression lissée
# Afficher la légende
plt.legend(loc='best')  # Modifier la position de la légende selon vos besoins
plt.show()
# Conclusion:
Tmessage("Pas de différences notables entre les deux modèles", Color='blue', Align='center', Size='14')
No description has been provided for this image
Pas de différences notables entre les deux modèles

Vérifier la colinéarité des variables

In [23]:
# Avec Intercept
Text_message("Facteur d'inflation de la variance avec Intercept")
varAI = results_AvecIntercept.model.exog
vif_AI = pd.DataFrame()
vif_AI["Variables"] = X_train.columns
vif_AI["VIF"] = [variance_inflation_factor(varAI, i) for i in np.arange(1,varAI.shape[1])]
display(vif_AI)
Tmessage("Coefficients inférieurs à 10 (Avec Intercept et Sans Intercept(data scalées))", 
         Color='black', Align='left', Size='10')
Tmessage("Il n'y a donc pas de problème de colinéarité", Color='black', Align='left', Size='10')

# Sans Intercept
Text_message("Facteur d'inflation de la variance sans Intercept")
varSI = results_SansIntercept.model.exog
vif_SI = pd.DataFrame()
vif_SI["Variables"] = X_train.columns
vif_SI["VIF"] = [variance_inflation_factor(varSI, i) for i in np.arange(0,varSI.shape[1])]
display(vif_SI)
Tmessage("On scale les données pour minimiser les différences de tailles des variables", 
         Color='black', Align='left', Size='10')

df =DF_Complet.copy()
scaler = preprocessing.StandardScaler()
data_array = scaler.fit_transform(df[['margin_low','height_right', 'margin_up', 'length']])
df = pd.DataFrame(data_array,  columns = ['margin_low','height_right', 'margin_up', 'length'])
variables=['height_left', 'height_right', 'margin_up', 'length']

# Séparation des données
train_df, test_df = train_test_split(df, test_size=0.2, random_state=random_state)

# Création du modèle de régression linéaire
model = smf.ols(formula='margin_low ~ height_right + margin_up + length -1', data=train_df)
# On fit
resultsSansIntercept = model.fit()
Text_message("Facteur d'inflation de la variance sans Intercept (data scalées)")
varSIscaled = resultsSansIntercept.model.exog
vif_SI = pd.DataFrame()
vif_SI["Variables"] = X_train.columns
vif_SI["VIF"] = [variance_inflation_factor(varSIscaled, i) for i in np.arange(0,varSIscaled.shape[1])]
display(vif_SI)
Tmessage("Coefficients inférieurs à 10 (Avec Intercept et Sans Intercept(data scalées))", 
         Color='black', Align='left', Size='10')
Tmessage("Il n'y a donc pas de problème de colinéarité", Color='black', Align='left', Size='10')
Tmessage("Pour les deux modèles, pas de problème de colinéarité", Color='blue', Align='center', Size='14')
Facteur d'inflation de la variance avec Intercept
Variables VIF
0 height_right 1.207999
1 margin_up 1.396446
2 length 1.519821
Coefficients inférieurs à 10 (Avec Intercept et Sans Intercept(data scalées))
Il n'y a donc pas de problème de colinéarité
Facteur d'inflation de la variance sans Intercept
Variables VIF
0 height_right 17037.112090
1 margin_up 261.096721
2 length 14793.687996
On scale les données pour minimiser les différences de tailles des variables
Facteur d'inflation de la variance sans Intercept (data scalées)
Variables VIF
0 height_right 1.208128
1 margin_up 1.396593
2 length 1.519928
Coefficients inférieurs à 10 (Avec Intercept et Sans Intercept(data scalées))
Il n'y a donc pas de problème de colinéarité
Pour les deux modèles, pas de problème de colinéarité

Tester l’homoscédasticité

NB : tester l’homoscédasticité, c'est tester la constance de la variance des résidus¶

In [24]:
from statsmodels.stats.diagnostic import het_breuschpagan
varSI = sm.add_constant(varSI)
varSI= pd.DataFrame(varSI)
# Test de Breusch-Pagan
test_result_bp = statsmodels.stats.diagnostic.het_breuschpagan(results_SansIntercept.resid, varSI)
Tmessage("Test de Breusch-Pagan:")
print("Statistique de test:", test_result_bp[0])
print("p-valeur:", test_result_bp[1])
print("L'effet de l'hétéroscédasticité est-il significatif ?", test_result_bp[1] < 0.05)
Tmessage("la variance des résidus n'est pas constante", Align='left', Color = 'blue')

# Calculer les résidus standardisés
residuals1 = results_SansIntercept.resid
# Créer un graphique de dispersion
plt.figure(figsize=(10, 4))
Tmessage("Homoscédasticité : Dispersion des Résidus 'Sans intercept'")
sns.scatterplot(x=varSI.index, y=residuals1, color='teal', alpha=0.7)
plt.ylabel("Résidus")
plt.axhline(0, color='red', linestyle='--', linewidth=2, label='Ligne Zéro') # Ajouter la ligne zéro
plt.legend()
plt.show()
Tmessage("p-valeur < à 5% pour les deux modèles - hypothèse 𝐻0 rejetée selon laquelle les variances sont constantes / hypothèse d’homoscédasticité",
        Color='blue')
Tmessage("------------------------------------------------------------------------------------------", Color='black')
# Test de Breusch-Pagan
test_result_bp = statsmodels.stats.diagnostic.het_breuschpagan(results_AvecIntercept.resid, varSI)
Tmessage("Test de Breusch-Pagan:")
print("Statistique de test:", test_result_bp[0])
print("p-valeur:", test_result_bp[1])
print("L'effet de l'hétéroscédasticité est-il significatif ?", test_result_bp[1] < 0.05)
Tmessage("la variance des résidus n'est pas constante", Align='left', Color = 'blue')

# Calculer les résidus standardisés
residuals = results_AvecIntercept.resid
# Créer un graphique de dispersion
plt.figure(figsize=(10, 4))
Tmessage("Homoscédasticité : Dispersion des Résidus 'Avec intercept'")
sns.scatterplot(x=varSI.index, y=residuals, color='teal', alpha=0.7)
plt.ylabel("Résidus")
plt.axhline(0, color='red', linestyle='--', linewidth=2, label='Ligne Zéro') # Ajouter la ligne zéro
plt.legend()
plt.show()
Tmessage("p-valeur < à 5% pour les deux modèles - hypothèse 𝐻0 rejetée selon laquelle les variances sont constantes / hypothèse d’homoscédasticité",
        Color='blue')
Tmessage("------------------------------------------------------------------------------------------", Color='black')
plt.figure(figsize=(10, 4))
Tmessage("Homoscédasticité : Dispersion des Résidus - Comparaison")
sns.scatterplot(x=varSI.index, y=residuals, color='teal', alpha=0.7, label="Avec intercept")
sns.scatterplot(x=varSI.index, y=residuals1, color='firebrick', alpha=0.7, label="Sans intercept")
plt.ylabel("Résidus")
plt.axhline(0, color='red', linestyle='--', linewidth=2, label='Ligne Zéro') # Ajouter la ligne zéro
plt.legend()
plt.show()
Test de Breusch-Pagan:
Statistique de test: 65.51362629168382
p-valeur: 3.894809736584079e-14
L'effet de l'hétéroscédasticité est-il significatif ? True
la variance des résidus n'est pas constante
Homoscédasticité : Dispersion des Résidus 'Sans intercept'
No description has been provided for this image
p-valeur < à 5% pour les deux modèles - hypothèse 𝐻0 rejetée selon laquelle les variances sont constantes / hypothèse d’homoscédasticité
------------------------------------------------------------------------------------------
Test de Breusch-Pagan:
Statistique de test: 68.51546882848272
p-valeur: 8.873495179862817e-15
L'effet de l'hétéroscédasticité est-il significatif ? True
la variance des résidus n'est pas constante
Homoscédasticité : Dispersion des Résidus 'Avec intercept'
No description has been provided for this image
p-valeur < à 5% pour les deux modèles - hypothèse 𝐻0 rejetée selon laquelle les variances sont constantes / hypothèse d’homoscédasticité
------------------------------------------------------------------------------------------
Homoscédasticité : Dispersion des Résidus - Comparaison
No description has been provided for this image

Tester la normalité des résidus

Si l'on veut tester la normalité des résidus, on peut faire un test de Shapiro-Wilk¶

In [25]:
Tmessage("Normalité des résidus", Size=18)

# Test de Shapiro-Wilk: 
Tmessage("Test de Shapiro-Wilk", Color='Blue',Size= 14)
Text_message("Modèle avec intercept")
stat, p_value = shapiro(results_AvecIntercept.resid)
Tmessage("Statistic: {}".format(stat), Align='left', Color='Black',Size= 12)
Tmessage("p-value: {}".format(p_value), Align='left', Color='Black',Size= 12)

Text_message("Modèle sans intercept")
stat, p_value = shapiro(results_SansIntercept.resid)
Tmessage("Statistic: {}".format(stat), Align='left', Color='Black',Size= 12)
Tmessage("p-value: {}".format(p_value), Align='left', Color='Black',Size= 12)

# Test de Kolmogorov-Smirnov:
Tmessage("Test de Kolmogorov-Smirnov", Color='Blue',Size= 14)
Text_message("Modèle avec intercept")
stat, p_value = kstest(results_AvecIntercept.resid, norm.cdf)
Tmessage("Statistic: {}".format(stat), Align='left', Color='Black',Size= 12)
Tmessage("p-value: {}".format(p_value), Align='left', Color='Black',Size= 12)

Text_message("Modèle sans intercept")
stat, p_value = kstest(results_SansIntercept.resid, norm.cdf)
Tmessage("Statistic: {}".format(stat), Align='left', Color='Black',Size= 12)
Tmessage("p-value: {}".format(p_value), Align='left', Color='Black',Size= 12)


fig, axes = plt.subplots(1, 2, figsize=(10, 3))  # 1 ligne, 2 colonnes

# Tracé du premier graphique
sns.distplot(results_SansIntercept.resid, ax=axes[1])
axes[1].set_title('Résidus sans intercept',color='firebrick')
# Tracé du deuxième graphique
sns.distplot(results_AvecIntercept.resid, ax=axes[0])
axes[0].set_title('Résidus avec intercept',color='firebrick')
# Ajustement de l'espacement entre les sous-graphiques
plt.tight_layout()
# Affichage des graphiques côte à côte
plt.show()

# Calcul des résidus sans intercept
resSansIntercept = results_SansIntercept.resid
# Calcul des résidus avec intercept
resAvecIntercept = results_AvecIntercept.resid

sns.set(rc={'axes.facecolor': 'white', 'figure.facecolor': 'white'})

fig, axes = plt.subplots(1, 2, figsize=(12, 4))  # Créer une grille de sous-graphiques : 1 ligne, 2 colonnes

# Premier Q-Q plot pour les résidus sans intercept
sm.qqplot(resSansIntercept, line='s', label="Résidus Sans Intercept", ax=axes[1])
axes[1].set_title("Hypothèse de la normalité des résidus : Q-Q plot (Sans Intercept)",color='firebrick')
axes[1].legend()

# Deuxième Q-Q plot pour les résidus avec intercept
sm.qqplot(resAvecIntercept, line='s', label="Résidus Avec Intercept", ax=axes[0])
axes[0].set_title("Hypothèse de la normalité des résidus : Q-Q plot (Avec Intercept)",color='firebrick')
axes[0].legend()

plt.tight_layout()  # Ajuster la disposition pour éviter les chevauchements
plt.show()
Tmessage("Les résidus ne suivent pas une distribution normale.", Color='firebrick',Size= 16)
Normalité des résidus
Test de Shapiro-Wilk
Modèle avec intercept
Statistic: 0.9879477331205726
p-value: 3.126863514043968e-08
Modèle sans intercept
Statistic: 0.9878644623092337
p-value: 2.8406897146394773e-08
Test de Kolmogorov-Smirnov
Modèle avec intercept
Statistic: 0.18878741830830323
p-value: 5.501821618615567e-37
Modèle sans intercept
Statistic: 0.18663864050907028
p-value: 3.7498676715437e-36
No description has been provided for this image
No description has been provided for this image
Les résidus ne suivent pas une distribution normale.
4.3 - Validation et utilisation du modèle pour remplacer les valeurs manquantes
In [26]:
# Création d'un Dataset avec les individus qui ont un marhin_low = NaN
DF_NaN = DF_F.loc[DF_F["margin_low"].isna()]

# Création d'un Dataset sans les valeurs manquantes
DF_Complet = DF_F.dropna()

# On créé un Dataset sans les valeurs manquantes
df =DF_Complet.copy()

# Séparation des données
train_df, test_df = train_test_split(df, test_size=0.2, random_state=random_state)
# Split Train
X_train = train_df[['height_right', 'margin_up', 'length']]
y_train = train_df['margin_low']
# Spli Test
X_test = test_df[['height_right', 'margin_up', 'length']]
y_test = test_df['margin_low']


Tmessage("Modèle sans Intercept")
# Création du modèle de régression linéaire
model = smf.ols(formula='margin_low ~ height_right + margin_up + length - 1', data=train_df)
# On fit
results_SansIntercept = model.fit()
# Prédictions sur Test
predictions_SansIntercept = results_SansIntercept.predict(X_test)

    # Créez un DataFrame contenant les caractéristiques pour prédiction
data_to_predict = X_test

    # Utilisez le modèle pour prédire les valeurs pour les données manquantes dans 'margin_low'
prediction_array = results_SansIntercept.predict(data_to_predict)

merged_df = pd.merge(X_test, y_test, left_index=True, right_index=True)

    # Remplacez les valeurs NaN dans 'margin_low' par les valeurs prédites
merged_df['margin_low_Predict'] = prediction_array
merged_df['% erreur'] = round(abs(100 - (merged_df['margin_low_Predict']*100 / merged_df['margin_low'])),2)
display(merged_df.head())
r_squared = round(r2_score(y_test,predictions_SansIntercept),4)
Text_message("Le coefficient de détermination R² est de {}".format(r_squared))
Text_message("Vérification erreur moyenne : {}  %".format(merged_df['% erreur'].mean().round(3)))

Tmessage("Modèle avec Intercept")
# Création du modèle de régression linéaire
model = smf.ols(formula='margin_low ~ height_right + margin_up + length ', data=train_df)
# On fit
results_AvecIntercept = model.fit()
# Prédictions sur Test
predictions_AvecIntercept = results_AvecIntercept.predict(X_test)

    # Créez un DataFrame contenant les caractéristiques pour prédiction
data_to_predict = X_test

    # Utilisez le modèle pour prédire les valeurs pour les données manquantes dans 'margin_low'
prediction_array = results_AvecIntercept.predict(data_to_predict)

merged_df = pd.merge(X_test, y_test, left_index=True, right_index=True)

    # Remplacez les valeurs NaN dans 'margin_low' par les valeurs prédites
merged_df['margin_low_Predict'] = prediction_array
merged_df['% erreur'] = round(abs(100 - (merged_df['margin_low_Predict']*100 / merged_df['margin_low'])),2)
display(merged_df.head())
r_squared = round(r2_score(y_test,predictions_AvecIntercept),4)
Text_message("Le coefficient de détermination R² est de {}".format(r_squared))
Text_message("Vérification erreur moyenne : {}  %".format(merged_df['% erreur'].mean().round(3)))


# ___________________________ Graphique de comparaison ___________________________
# Récupération des valeurs prédites et des résidus sans intercept
predicted_values = results_SansIntercept.fittedvalues
residuals = results_SansIntercept.resid
# Récupération des valeurs prédites et des résidus avec intercept
predicted_values1 = results_AvecIntercept.fittedvalues
residuals1 = results_AvecIntercept.resid

# Création d'un DataFrame pour le graphique des résidus
residuals_df = pd.DataFrame({'Valeurs prédites': predicted_values, 'Résidus': residuals})
residuals_df1 = pd.DataFrame({'Valeurs prédites': predicted_values1, 'Résidus': residuals1})

# Tracé du graphique des résidus
sns.set(rc={'axes.facecolor': 'white', 'figure.facecolor': 'white'})
plt.figure(figsize=(12,4))
sns.residplot(data=residuals_df, x='Valeurs prédites', y='Résidus', color='teal', scatter_kws={'alpha': 0.8}, lowess=True, 
              line_kws={'color': 'firebrick', 'linewidth': 6})
sns.residplot(data=residuals_df1, x='Valeurs prédites', y='Résidus', color='orange', scatter_kws={'alpha': 0.5}, lowess=True, 
              line_kws={'color': 'blue', 'linewidth': 2})

plt.xlabel('Valeurs prédites')
plt.ylabel('Résidus')
plt.title('Graphique des résidus pour une régression linéaire multiple', y=1, 
          fontdict={'size': 14, 'weight': 'bold', 'style': 'italic', 'color': 'firebrick'})
# Ajout de légendes factices pour le graphique
plt.scatter([], [], color='teal', label='Sans intercept')  # Élément pour les points dispersés
plt.plot([], [], color='firebrick', linewidth=2, label='Régression lissée')  # Élément pour la ligne de régression lissée
plt.scatter([], [], color='orange', label='Avec intercept')  # Élément pour les points dispersés
plt.plot([], [], color='blue', linewidth=2, label='Régression lissée')  # Élément pour la ligne de régression lissée
# Afficher la légende
plt.legend(loc='best')  # Modifier la position de la légende selon vos besoins
plt.show()
# Conclusion:
Tmessage("Pas de différences notables entre les deux modèles", Color='blue', Align='center', Size='14')
Modèle sans Intercept
height_right margin_up length margin_low margin_low_Predict % erreur
1301 104.19 3.25 111.99 5.72 4.882714 14.64
1028 103.93 3.49 111.34 5.38 5.093796 5.32
281 104.21 3.07 113.01 4.18 4.447908 6.41
665 104.44 2.99 113.16 4.54 4.465495 1.64
555 104.10 2.70 113.99 4.47 3.906402 12.61
Le coefficient de détermination R² est de 0.4796
Vérification erreur moyenne : 7.122 %
Modèle avec Intercept
height_right margin_up length margin_low margin_low_Predict % erreur
1301 104.19 3.25 111.99 5.72 4.867881 14.90
1028 103.93 3.49 111.34 5.38 5.145473 4.36
281 104.21 3.07 113.01 4.18 4.387276 4.96
665 104.44 2.99 113.16 4.54 4.361852 3.92
555 104.10 2.70 113.99 4.47 3.829804 14.32
Le coefficient de détermination R² est de 0.4878
Vérification erreur moyenne : 7.115 %
No description has been provided for this image
Pas de différences notables entre les deux modèles

Avec une erreur moyenne équivalente et un R² à 0.488, le modèle avec Intercept est validé

In [27]:
Tmessage("Imputation des valeurs 'margin_low' dans DF_NaN selon le modèle validé")
# Création d'un DataFrame contenant les caractéristiques pour prédiction
data_to_predict = DF_NaN[['height_right', 'margin_up', 'length']]

# Utilisation du modèle pour prédire les valeurs pour les données manquantes dans 'margin_low'
prediction_array = results_AvecIntercept.predict(data_to_predict)

# Remplacement des valeurs NaN dans 'margin_low' par les valeurs prédites
DF_NaN['margin_low'] = prediction_array.round(3)
display(DF_NaN.head(4))
Text_message("Nombres de lignes impactées : {} lignes".format(DF_NaN.shape[0]))
Imputation des valeurs 'margin_low' dans DF_NaN selon le modèle validé
is_genuine diagonal height_left height_right margin_low margin_up length
72 1 171.94 103.89 103.45 4.329 3.25 112.79
99 1 171.93 104.07 104.18 4.371 3.14 113.08
151 1 172.07 103.80 104.38 4.452 3.02 112.93
197 1 171.45 103.66 103.80 4.335 3.62 113.27
Nombres de lignes impactées : 37 lignes
In [28]:
Tmessage("--------------------------- Graphiques d'interprétation ------------------------",Color='blue')
DFC = DF_Complet.copy()
DFN = DF_NaN.copy()
# Création de la figure et des sous-graphiques
fig, axs = plt.subplots(1, 3, figsize=(12, 4))

# Tracé des graphiques dans chaque sous-graphique
plot_regression(DFC, DFN, 'length', "'margin-low' en fonction de 'length'", axs[0])
plot_regression(DFC, DFN, 'height_right', "'margin-low' en fonction de 'height_right'", axs[1])
plot_regression(DFC, DFN, 'margin_up', "'margin-low' en fonction de 'margin_up'", axs[2])

# Rétablir les valeurs originales pour éviter les modifications permanentes dans les DataFrames
DFC['is_genuine'] = DFC['is_genuine'].replace({'Faux': 0, 'Vrai': 1})
DFN['is_genuine'] = DFN['is_genuine'].replace({'Faux': 0, 'Vrai': 1})

# Récupérer les positions des axes
pos1 = axs[0].get_position()
pos2 = axs[1].get_position()

# Créer une légende commune
fig.legend(labels=['DF_Complet','Faux billets', 'Vrais billets', 'Régression linéaire',
                   'Interval de confiance', 'DF_NaN', 'Prédict Vrais billets', 'Prédict Faux billets'],
           loc='upper center', bbox_to_anchor=((pos1.x0 + pos2.x1) / 1.5, pos1.y1-0.9), ncol=4)
plt.show()
--------------------------- Graphiques d'interprétation ------------------------
No description has been provided for this image

Intégration des valeurs prédites dans le DataFrame

In [29]:
# Remplacer les valeurs NaN dans DF_F["margin_low"] par les valeurs correspondantes de DF_NaN
DF_F["margin_low"].fillna(DF_NaN["margin_low"], inplace=True)

# Vérification des valeurs manquantes restantes
DF_F.loc[DF_F["margin_low"].isna()]
Out[29]:
is_genuine diagonal height_left height_right margin_low margin_up length
5 - K-means
5.1 - Visualisation via le modèle K-means des variables les plus prédictives - "margin_low" et "length"
In [30]:
Var1 = "margin_low"
Var2 = "length"

DF_kmeans = DF_F.copy()
# On affecte à nos X et y les données nécessaires
X = DF_kmeans.drop("is_genuine", axis=1)
y = DF_kmeans["is_genuine"]

# Définition du nombre de clusters avec graphique
n_cluster = kmeans_coude(X, graphique=True)

# Centrage et réduction
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)
# Création d'un DF: 
X_scaledGlobal = pd.DataFrame(X_scaled, columns=X.columns)

# On applique le modèle
k_means = KMeans(init="k-means++", n_clusters=n_cluster, random_state = random_state , n_init = 'auto'  )
k_means.fit(X_scaled)

# On prédit suivant le modèle
prediction = k_means.predict(X_scaled)
# Ajout de la colonne  'Cluster'
X_scaledGlobal["cluster"] = prediction

# Calcule de la performance du modèle
test_f1 = f1_score(y, prediction, average='weighted')
Text_message("F1-score sur l'ensemble du DataFrame: {} %".format(round(test_f1*100,4)))

# Affichage du nuage de points (individus) en cluster avec les centroids
fig = plt.figure(figsize=(12, 4))
# Définir des couleurs pour chaque cluster
cluster_colors = ['indianred', 'lightseagreen']
# Nuage de points pour les individus
for cluster in range(n_cluster):
    plt.scatter(X_scaledGlobal.loc[X_scaledGlobal['cluster'] == cluster, Var1],
                X_scaledGlobal.loc[X_scaledGlobal['cluster'] == cluster, Var2],
                c=cluster_colors[cluster], marker='o', s=30, edgecolors='w', 
                label='Vrais' if cluster == 1 else 'Faux')

# Centroids
plt.scatter(k_means.cluster_centers_[:, X_scaledGlobal.columns.get_loc(Var1)],
            k_means.cluster_centers_[:, X_scaledGlobal.columns.get_loc(Var2)],
            marker='p', s=150, c='darkred', edgecolors='w', label='Centroids')

plt.grid()
Tmessage('Clustering via K-means des variables les plus prédictives : {} et {}'.format(Var1, Var2), Color="firebrick", Size =14)
plt.xlabel(Var1)
plt.ylabel(Var2)
# Ajouter la légende
plt.legend()
plt.show()


# On réalise notre matrice de confusion
cf = confusion_matrix(y, X_scaledGlobal.cluster)
cm = ConfusionMatrixDisplay(cf)

# Enregistrement des valeurs de la matrice de confusion
true_negatives = cf[0, 0]
false_positives = cf[0, 1]
false_negatives = cf[1, 0]
true_positives = cf[1, 1]


# Etiquettes et fréquences
labels, counts = np.unique(k_means.labels_, return_counts=True)
Tmessage('- Cluster {}: {} Faux billets dont'.format(labels[0], counts[0]), Align='left', Color='black')
Tmessage("true_negatives : {}  -  false_negatives : {}".format(true_negatives,false_negatives), 
         Align = 'left', Color="firebrick", Size =14)
Tmessage('- Cluster {}: {} Vrais billets dont'.format(labels[1], counts[1]), Align='left', Color='black')
Tmessage("true_positives : {}  -  false_positives : {}".format(true_positives,false_positives), 
         Align = 'left', Color="firebrick", Size =14)

# Tracer la matrice de confusion 
Tmessage("Matrice de Confusion", Color="firebrick", Size =14)
fig, ax = plt.subplots(figsize=(6, 4))

title = "is_genuine en fonction des 6 variables prédictives" 
cm.plot(ax=ax, cmap=sns.color_palette("mako", as_cmap=True), xticks_rotation='horizontal') 
ax.set_title(title, color='firebrick', fontsize=14) 
plt.xlabel("Prédiction")
plt.ylabel("Vraie valeur")
plt.grid(False)

plt.show()
La méthode du coude nous indique un nombre optimal de 2 clusters
No description has been provided for this image
F1-score sur l'ensemble du DataFrame: 98.4655 %
Clustering via K-means des variables les plus prédictives : margin_low et length
No description has been provided for this image
- Cluster 0: 497 Faux billets dont
true_negatives : 487 - false_negatives : 10
- Cluster 1: 1003 Vrais billets dont
true_positives : 990 - false_positives : 13
Matrice de Confusion
No description has been provided for this image
5.2 - Création du modèle K-means en utilisant train_test_split
In [31]:
DF_kmeans = DF_F.copy()
# On affecte à nos X et y les données nécessaires
X = DF_kmeans.drop("is_genuine", axis=1)
y = DF_kmeans["is_genuine"]
# Centrage et réduction
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)
# Création d'un DF: 
X_scaled = pd.DataFrame(X_scaled, columns=X.columns)

# On créer notre train set et test set
X_train, X_test, y_train, y_test = train_test_split(X_scaled, y, test_size = 0.3, random_state=random_state)

# Définition du nombre de clusters avec graphique
#n_cluster = kmeans_coude(X_train, graphique=True)

scoreKmeans = 0
while scoreKmeans <0.5:
    # On affecte le KMeans() à la variable model_Kmeans
    model_Kmeans = KMeans(init="k-means++", n_clusters=n_cluster, n_init = 'auto'  )
    # On entraine lt model
    model_Kmeans.fit(X_train)
    # On calcule les centroides
    cluster_centers = model_Kmeans.cluster_centers_
    #if 'cluster' in X_test.columns:
       # X_test = X_test.drop("cluster", axis=1)
    # On stocke les predictions
    prediction = model_Kmeans.predict(X_test)
    prediction1 = model_Kmeans.predict(X_train)
    # Ajout de la colonne  'Cluster'
    X_test["cluster"] = prediction
    X_train["cluster"] = prediction1
    scoreKmeans = f1_score(y_test, prediction, average='weighted')
    #print(scoreKmeans)
   
    
#print("silhouette_score Test : ", silhouette_score(X_test,prediction))    
#print("silhouette_score Train : ", silhouette_score(X_train,prediction1))      
    
Tmessage("Performance du modèle K-means sur le set Test", Color="blue", Size=20, Police='Scriptina')    
# Affichage du nuage de points (individus) en cluster avec les centroids
fig = plt.figure(figsize=(12, 4))
# Définir des couleurs pour chaque cluster
cluster_colors = ['indianred', 'lightseagreen']
# Nuage de points pour les individus
for cluster in range(n_cluster):
    plt.scatter(X_test.loc[X_test['cluster'] == cluster, Var1],
                X_test.loc[X_test['cluster'] == cluster, Var2],
                c=cluster_colors[cluster], marker='o', s=30, edgecolors='w', 
                label='Vrais' if cluster == 1 else 'Faux')

# Centroids
plt.scatter(model_Kmeans.cluster_centers_[:, X_test.columns.get_loc(Var1)],
            model_Kmeans.cluster_centers_[:, X_test.columns.get_loc(Var2)],
            marker='p', s=150, c='darkred', edgecolors='w', label='Centroids')

plt.grid()
Tmessage('Clustering via K-means des variables les plus prédictives : {} et {}'.format(Var1, Var2), Color="firebrick", Size =14)
plt.xlabel(Var1)
plt.ylabel(Var2)
# Ajouter la légende
plt.legend()
plt.show()
# Mesure de la précision du modèle
test_f = f1_score(y_test, prediction, average='weighted')
Tmessage("Précision du modèle sur le set Test {} %".format(round(test_f*100,3)), Align='left', Color="teal")

# On réalise notre matrice de confusion
cf = confusion_matrix(y_test, X_test.cluster)
cm = ConfusionMatrixDisplay(cf)

# Enregistrement des valeurs de la matrice de confusion
true_negatives = cf[0, 0]
false_positives = cf[0, 1]
false_negatives = cf[1, 0]
true_positives = cf[1, 1]


# Etiquettes et fréquences
#labels, counts = np.unique(k_means.labels_, return_counts=True)
labels, counts = np.unique(prediction, return_counts=True)
Tmessage('- Cluster {}: {} Faux billets dont'.format(labels[0], counts[0]), Align='left', Color='black')
Tmessage("true_negatives : {}  -  false_negatives : {}".format(true_negatives,false_negatives), 
         Align = 'left', Color="firebrick", Size =14)
Tmessage('- Cluster {}: {} Vrais billets dont'.format(labels[1], counts[1]), Align='left', Color='black')
Tmessage("true_positives : {}  -  false_positives : {}".format(true_positives,false_positives), 
         Align = 'left', Color="firebrick", Size =14)

classes = ["Vrai", "Faux"]
# Tracer la matrice de confusion 
Tmessage("Matrice de Confusion", Color="firebrick", Size =14)
fig, ax = plt.subplots(figsize=(6, 4))

title = "is_genuine en fonction des 6 variables prédictives" 
#sns.heatmap(cm, annot=True, fmt="", cmap="mako", xticklabels=classes, yticklabels=classes)

cm.plot(ax=ax, cmap=sns.color_palette("mako", as_cmap=True), xticks_rotation='horizontal') 
ax.set_title(title, color='firebrick', fontsize=14) 
plt.xlabel("Prédiction")
plt.ylabel("Vraie valeur")
plt.grid(False)
# Modifier les étiquettes des axes
ax.set_xticklabels(["Faux", "Vrai"])
ax.set_yticklabels(["Faux", "Vrai"])
plt.show()



Tmessage("Performance du modèle K-means sur le set Train", Color="blue", Size=20, Police='Scriptina')    
# Affichage du nuage de points (individus) en cluster avec les centroids
fig = plt.figure(figsize=(12, 4))
# Définir des couleurs pour chaque cluster
cluster_colors = ['indianred', 'lightseagreen']
# Nuage de points pour les individus
for cluster in range(n_cluster):
    plt.scatter(X_train.loc[X_train['cluster'] == cluster, Var1],
                X_train.loc[X_train['cluster'] == cluster, Var2],
                c=cluster_colors[cluster], marker='o', s=30, edgecolors='w', 
                label='Vrais' if cluster == 1 else 'Faux')

# Centroids
plt.scatter(k_means.cluster_centers_[:, X_train.columns.get_loc(Var1)],
            k_means.cluster_centers_[:, X_train.columns.get_loc(Var2)],
            marker='p', s=150, c='darkred', edgecolors='w', label='Centroids')

plt.grid()
Tmessage('Clustering via K-means des variables les plus prédictives : {} et {}'.format(Var1, Var2), Color="firebrick", Size =14)
plt.xlabel(Var1)
plt.ylabel(Var2)
# Ajouter la légende
plt.legend()
plt.show()
# Mesure de la précision du modèle
test_f1 = f1_score(y_train, prediction1, average='weighted')
Tmessage("Précision du modèle sur le set Train {} %".format(round(test_f1*100,3)), Align='left', Color="teal")

# On réalise notre matrice de confusion
cf = confusion_matrix(y_train, X_train.cluster)
cm = ConfusionMatrixDisplay(cf)

# Enregistrement des valeurs de la matrice de confusion
true_negatives = cf[0, 0]
false_positives = cf[0, 1]
false_negatives = cf[1, 0]
true_positives = cf[1, 1]


# Etiquettes et fréquences
#labels, counts = np.unique(k_means.labels_, return_counts=True)
labels, counts = np.unique(prediction1, return_counts=True)
Tmessage('- Cluster {}: {} Faux billets dont'.format(labels[0], counts[0]), Align='left', Color='black')
Tmessage("true_negatives : {}  -  false_negatives : {}".format(true_negatives,false_negatives), 
         Align = 'left', Color="firebrick", Size =14)
Tmessage('- Cluster {}: {} Vrais billets dont'.format(labels[1], counts[1]), Align='left', Color='black')
Tmessage("true_positives : {}  -  false_positives : {}".format(true_positives,false_positives), 
         Align = 'left', Color="firebrick", Size =14)

# Tracer la matrice de confusion 
Tmessage("Matrice de Confusion", Color="firebrick", Size =14)
fig, ax = plt.subplots(figsize=(6, 4))

title = "is_genuine en fonction des 6 variables prédictives" 
cm.plot(ax=ax, cmap=sns.color_palette("mako", as_cmap=True), xticks_rotation='horizontal') 
ax.set_title(title, color='firebrick', fontsize=14) 
plt.grid(False)
plt.xlabel("Prédiction")
plt.ylabel("Vraie valeur")
# Modifier les étiquettes des axes
ax.set_xticklabels(["Faux", "Vrai"])
ax.set_yticklabels(["Faux", "Vrai"])
plt.show()
Performance du modèle K-means sur le set Test
Clustering via K-means des variables les plus prédictives : margin_low et length
No description has been provided for this image
Précision du modèle sur le set Test 98.443 %
- Cluster 0: 152 Faux billets dont
true_negatives : 149 - false_negatives : 3
- Cluster 1: 298 Vrais billets dont
true_positives : 294 - false_positives : 4
Matrice de Confusion
No description has been provided for this image
Performance du modèle K-means sur le set Train
Clustering via K-means des variables les plus prédictives : margin_low et length
No description has been provided for this image
Précision du modèle sur le set Train 98.664 %
- Cluster 0: 341 Faux billets dont
true_negatives : 337 - false_negatives : 4
- Cluster 1: 709 Vrais billets dont
true_positives : 699 - false_positives : 10
Matrice de Confusion
No description has been provided for this image
In [32]:
Var1 = "margin_low"
Var2 = "length"
Tmessage("Visualisation via K-means des variables les plus prédictives sur X-train", Color='black')
yt = pd.DataFrame(y)
yt.reset_index(drop=True, inplace=True)

# Ajouter les labels de cluster au DataFrame
X_train['cluster'] = model_Kmeans.labels_

# Affichage du nuage de points (individus) en cluster avec les centroids
fig = plt.figure(figsize=(12, 4))
# Définir des couleurs pour chaque cluster
cluster_colors = ['indianred', 'lightseagreen']

# Nuage de points pour les individus
for cluster in range(n_cluster):
    cluster_data = X_train.loc[X_train['cluster'] == cluster]
    cluster_data = pd.merge(cluster_data, yt, left_index=True, right_index=True)    
    plt.scatter(cluster_data[Var1], cluster_data[Var2],
                c=cluster_colors[cluster], marker='o', s=30, edgecolors='w', 
                label='Vrais' if cluster == 1 else 'Faux')

    # Afficher les indices dans la série y
    for i, index in enumerate(cluster_data.index):
        plt.text(cluster_data.loc[index, Var1], cluster_data.loc[index, Var2], str(cluster_data.loc[index,'is_genuine']), 
                 fontsize=8, ha='center', va='center')

# Centroids
plt.scatter(model_Kmeans.cluster_centers_[:, X_train.columns.get_loc(Var1)],
            model_Kmeans.cluster_centers_[:, X_train.columns.get_loc(Var2)],
            marker='p', s=150, c='darkred', edgecolors='w', label='Centroids')

plt.grid()
plt.title('Clustering avec KMeans en fonction de {} et {}'.format(Var1, Var2), color="firebrick", size=14)
plt.xlabel(Var1)
plt.ylabel(Var2)

# Ajouter la légende
plt.legend()

plt.show()
Visualisation via K-means des variables les plus prédictives sur X-train
No description has been provided for this image
5.3 - PCA
In [33]:
# Préparation des données:
scaler=StandardScaler()
DF_PCA = DF_F.iloc[:,1:7]
X_norm=scaler.fit_transform(DF_PCA)

# PCA
pca=PCA()
pca.fit(X_norm)
features = DF_PCA.columns
n_components = len(pca.explained_variance_ratio_)

Tmessage('Analyse des composantes principales', Color='blue', Size=20)

# Vérification moyennes à 0 et écarts type à 1 :
Text_message("Vérification moyennes à 0 et écarts type à 1")
idx = ["mean", "std"]
display(pd.DataFrame(X_norm).describe().round(2).loc[idx, :])

# On instancie notre ACP :
pca = PCA(n_components=n_components)
# On l'entraine sur les données scalées :
pca.fit(X_norm)


# Get the centroids
kmeans = KMeans(n_clusters=2, random_state=random_state) 
kmeans.fit(X_norm)
centroids = pca.transform(kmeans.cluster_centers_)


# Variable avec la liste de nos composantes :
x_list = range(1, n_components+1)

# Enregistrement dans une variable :
scree = (pca.explained_variance_ratio_*100).round(2)
scree_cum = scree.cumsum().round()

# --------------------------------------------------- Analyse des composantes principales ---------------------------------
# Affichage des graphiques
plt.figure(figsize=(12, 4))

plt.subplot(1, 2, 1)
plt.bar(x_list, scree, color=plt.cm.Blues(scree / max(scree)))
plt.plot(x_list, scree_cum, c="firebrick", marker='o')
plt.xlabel("Rang de l'axe d'inertie")
plt.ylabel("Pourcentage d'inertie")
plt.title("Eboulis des valeurs propres", color='firebrick', fontsize=10, fontweight='bold')
plt.grid(color='gray', linestyle='--')
plt.subplot(1, 2, 2)

# Calcul de la variance expliquée par composante
explained_variance = pca.explained_variance_ratio_
# Création d'un graphique pour la méthode du coude
plt.plot(range(1, len(explained_variance) + 1), explained_variance, linestyle='-', c="firebrick", marker='o')
plt.xlabel('Nombre de composantes')
plt.ylabel('Variance expliquée')
plt.title('Variance expliquée par nombre de composantes\n Méthode du coude', color='firebrick', fontsize=10, fontweight='bold')
plt.grid(color='gray', linestyle='--')
# Ajustement des ticks de l'axe des abscisses
plt.xticks(np.arange(1, len(explained_variance) + 1, 1))
plt.show()

# Affichage n-component retenu :
Tmessage('Cumul des valeurs propres : {}'.format(scree_cum), Color='teal')

# DF des composantes principales 
pcs = pca.components_
pcs = pd.DataFrame(pcs)
# Ajout des F :
pcs.columns = features
pcs.index = [f"F{i}" for i in x_list]
pcs.round(2)
Tmessage("Composantes principales")
display(pcs)

# Représentation graphique :
fig, ax = plt.subplots(figsize=(12, 3))
sns.heatmap(pcs.T, vmin=-1, vmax=1, annot=True, cmap="coolwarm", fmt="0.2f")
plt.title('Heatmap des composantes principales', color='firebrick', fontsize=14, fontweight='bold')
plt.show()


# -------------------------------------------- Cercle des corrélations des composantes principales ----------------------------
Tmessage('-------------------------------------------------------------------------------------------', Color='black', Size=20)
Tmessage('Projection des individus sur le premier plan factoriel', Color='blue', Size=20)
# Préparation du set "composantes retenues"
pcs = pcs.T
pcs_final = pcs[['F1','F2']]

# Représentation graphique :
fig, ax = plt.subplots(figsize=(12, 2))
sns.heatmap(pcs_final, vmin=-1, vmax=1, annot=True, cmap="coolwarm", fmt="0.4f")
plt.title('Heatmap des composantes principales suffisantes ({} %)'.format(scree_cum[1]), color='firebrick', fontsize=14, 
          fontweight='bold')
plt.show()
features = DF_F.iloc[:,1:7].columns
# correlation_graph(pca, x_y, features, palette="rocket", legend_fontsize=12, label_fontsize=10, Indicsize=8, arrow_alpha=0.8):
# Afficher le cercle de corrélation pour F1 et F2
x_y = 0,1
correlation_graph(pca, (x_y), features, palette="viridis")

# Projection 
X_proj = pca.transform(X_norm)
# Plans_Factoriels
Plans_Factoriels(X_proj, x_y, pca, clusters=X_scaledGlobal["cluster"], centroids=centroids, figsize=(10,4), marker="o", 
                 palette=['darkslategrey', 'lightseagreen'])
Analyse des composantes principales
Vérification moyennes à 0 et écarts type à 1
0 1 2 3 4 5
mean -0.0 0.0 -0.0 0.0 -0.0 0.0
std 1.0 1.0 1.0 1.0 1.0 1.0
No description has been provided for this image
Cumul des valeurs propres : [ 43. 60. 73. 85. 95. 100.]
Composantes principales
diagonal height_left height_right margin_low margin_up length
F1 -0.084405 0.330424 0.393509 0.507553 0.439674 -0.527184
F2 0.941360 0.307524 0.108083 -0.071863 -0.005511 0.048925
F3 -0.288752 0.885328 -0.160049 -0.112410 -0.268913 0.149166
F4 -0.101564 -0.051433 0.867907 -0.091625 -0.440962 0.175884
F5 -0.113900 0.098681 0.233638 -0.563831 0.714347 0.307416
F6 0.007136 0.007667 0.002066 0.631101 0.172057 0.756302
No description has been provided for this image
-------------------------------------------------------------------------------------------
Projection des individus sur le premier plan factoriel
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
In [34]:
DF_kmeans = DF_F.copy()
T_size = 0.3
# On affecte à nos X et y les données nécessaires
X = DF_kmeans.drop("is_genuine", axis=1)
y = DF_kmeans["is_genuine"]
# Centrage et réduction
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)
# Création d'un DF: 
X_scaled = pd.DataFrame(X_scaled, columns=X.columns)
# On créer notre train set et test set
X_train, X_test, y_train, y_test = train_test_split(X_scaled, y, test_size = T_size, random_state=random_state)

# Création du modèle et vérification que les clusters sont répartis de la bonne façon à l'aide du f1_score
scoreKmeans = 0
while scoreKmeans <0.5:
    # On applique le modèle
    model_Kmeans = KMeans(init="k-means++", n_clusters=n_cluster, n_init = 'auto'  )
    #model_Kmeans =KMeans(n_cluster)
    # On entraine lt model
    model_Kmeans.fit(X_train)
    # On calcule les centroides
    #cluster_centers = pca.transform(model_Km.cluster_centers_)
    # Get the centroids
    #kmeans = KMeans(n_cluster)  
    #kmeans.fit(X_norm)
    #centroids = pca.transform(model_Km.cluster_centers_)
    if 'cluster' in X_test.columns:
        X_test = X_test.drop("cluster", axis=1)
    # On stocke les predictions
    prediction = model_Kmeans.predict(X_test)
    # Ajout de la colonne  'Cluster'
    X_test["cluster"] = prediction
    scoreKmeans = f1_score(y_test, prediction, average='weighted')
    # print(scoreKmeans)
    
# Get the centroids
centroids = pca.transform(model_Kmeans.cluster_centers_) 

# Affichage du dataset :
Tmessage("Affectation des clusters au set test ({} % du DataFrame)".format(round(T_size*100),0))
display(X_test)

# Calcul des métriques sur les données de test
test_precision_Kmeans = precision_score(y_test, prediction, average='weighted')
test_recall_Kmeans = recall_score(y_test, prediction, average='weighted')
F1_Kmeans = f1_score(y_test, prediction, average='weighted')

# Affichage des résultats
Text_message("F1-score sur l'ensemble de test: {} %".format(round(F1_Kmeans*100,4)))
#print("Précision sur l'ensemble d'entraînement:", train_precision)
Text_message("Précision sur l'ensemble de test: {}".format(round((test_precision_Kmeans),4)))
#print("\nRappel sur l'ensemble d'entraînement:", train_recall)
Text_message("Rappel sur l'ensemble de test: {}".format(round((test_recall_Kmeans),4)))

# Matrice de confusion
cf = confusion_matrix(y_test, X_test.cluster)
cm = ConfusionMatrixDisplay(cf)
# Définition du titre
title = "Matrice de Confusion"
# Tracer la matrice de confusion avec un titre personnalisé et en changeant la palette de couleurs
fig, ax = plt.subplots(figsize=(10, 4))
cm.plot(ax=ax, cmap='mako', xticks_rotation='horizontal')  # Changer 'Blues' à la palette de couleur désirée
ax.set_title(title, color='firebrick', fontsize=14)  # Définir la taille de la police du titre
plt.grid(False)
plt.show()


# Projection 
x_y = 0,1
X_test1 = X_test.drop("cluster", axis=1)
#X_norm=scaler.fit_transform(X_test1)

#pca=PCA()
#pca.fit(X_norm)
X_proj = pca.transform(X_test1)
Plans_Factoriels(X_proj, x_y, pca, clusters=X_test["cluster"], centroids=centroids, figsize=(10,4), marker="o", 
                 palette=['teal','grey'])


#scaler=StandardScaler()
scaler=MinMaxScaler()
# Réduction de la dimension des données à 2 dimensions avec T-SNE
tsne = TSNE(n_components=n_cluster, random_state=random_state)
X_tsne = tsne.fit_transform(X_test1)

# Ajout de l'indice du cluster dans les données pour la visualisation
X_tsne_with_clusters = np.column_stack((X_tsne, prediction))

# Création d'un DataFrame pour faciliter la visualisation
df_tsne_clusters = pd.DataFrame(data=X_tsne_with_clusters, columns=['Dimension 1', 'Dimension 2', 'Cluster'])


# Visualisation des clusters
Tmessage('Visualisation des clusters avec T-SNE', Color='teal', Size = 18)
plt.figure(figsize=(10, 3))
#scatter = plt.scatter(df_tsne_clusters['Dimension 1'], df_tsne_clusters['Dimension 2'], 
                      #c=df_tsne_clusters['Cluster'], cmap=ListedColormap(['teal', 'gray']))
scatter = sns.scatterplot(x='Dimension 1', y='Dimension 2', data=df_tsne_clusters, hue='Cluster', 
                          palette=['teal', 'firebrick'])

plt.xlabel('Dimension 1')
plt.ylabel('Dimension 2')
# Obtenir les éléments de légende
handles, labels = scatter.get_legend_handles_labels()

# Utilisation des éléments de légende pour créer la légende
plt.legend(handles, labels, title='Cluster')
#plt.legend(*scatter.legend_elements(), title='Cluster')
plt.show()
#df_tsne_clusters


# Calcul du score de silhouette
silhouette_avg = silhouette_score(X_test, prediction)
Text_message("Score de silhouette : {}".format(round(silhouette_avg,4)))
# Calcul du Davies-Bouldin Index
db_index = davies_bouldin_score(X_test, prediction)
Text_message("Davies-Bouldin Index : {}".format(round(db_index,4)))
# Calcul de la Calinski-Harabasz Index
ch_index = calinski_harabasz_score(X_test, prediction)
Text_message("Calinski-Harabasz Index : {}".format(round(ch_index,4)))
# Calcul de l'Adjusted Rand Index (ARI)
ari = adjusted_rand_score(y_test, prediction)
Text_message("Adjusted Rand Index (ARI) : {}".format(round(ari,4)))
Affectation des clusters au set test (30 % du DataFrame)
diagonal height_left height_right margin_low margin_up length cluster
1212 -0.191548 1.003691 1.473628 0.328705 0.684084 -0.640159 0
954 -0.617647 -1.334617 -1.014709 -1.461412 0.727236 0.104878 1
1207 -0.814308 0.302198 0.674903 0.541092 0.986152 -1.144493 0
921 -1.961498 -1.033977 2.579556 -0.475331 -1.171478 0.528977 1
89 -0.060441 -1.802278 -0.308144 -0.369137 0.166253 0.425818 1
... ... ... ... ... ... ... ...
830 -1.535399 -0.299081 -0.215984 -0.247773 0.727236 1.136469 1
1350 -1.895944 -0.833551 -0.676787 1.117570 1.201915 -1.236190 0
1044 -1.338738 0.502625 -0.277424 -0.035387 1.158762 -1.236190 0
705 1.643956 -0.933764 0.306260 -0.338796 0.295710 1.090621 1
417 2.528931 -0.031846 0.797784 -1.127662 0.209405 0.964538 1

450 rows × 7 columns

F1-score sur l'ensemble de test: 98.4432 %
Précision sur l'ensemble de test: 0.9844
Rappel sur l'ensemble de test: 0.9844
No description has been provided for this image
No description has been provided for this image
Visualisation des clusters avec T-SNE
No description has been provided for this image
Score de silhouette : 0.3506
Davies-Bouldin Index : 1.1814
Calinski-Harabasz Index : 255.5685
Adjusted Rand Index (ARI) : 0.938

Un ARI à 0.938 indique une correspondance très forte entre les deux ensembles d'étiquettes. Le CHI indique une bonne séparation entre les clusters dans l'algorithme de clustering

Pour ce projet, il est intéressant d'obtenir de bons résultats avec le K-mean (Algorithme non supervisé)

6 - Régression logistique
6.1 - Régression Logistique Statsmodels et Sklearn

Régression logistique avec Statsmodels

On utilise la fonction 'backward_selected_log' pour définir le nombre de variables prédictives¶

In [35]:
# On utilise la fonction my_backward_selected_logistic afin de trouver les varibles descriptives les plus pertinentes
# Suppression des variables dont la p-value > 5%
columns = ['margin_low','diagonal','is_genuine','height_left','height_right','margin_up','length']
reg_backward = backward_selected_log(DF_F[columns], 'is_genuine', 0.05)
_______________________________
is_genuine ~ height_right + length + diagonal + margin_up + margin_low + height_left + 1
Optimization terminated successfully.
         Current function value: 0.028229
         Iterations 13
remove diagonal (p-value: 0.922 )

_______________________________
is_genuine ~ height_right + length + margin_up + margin_low + height_left + 1
Optimization terminated successfully.
         Current function value: 0.028233
         Iterations 12
remove height_left (p-value: 0.112 )

_______________________________
is_genuine ~ height_right + length + margin_up + margin_low + 1
Optimization terminated successfully.
         Current function value: 0.029097
         Iterations 12
is the final model!

Logit Regression Results
Dep. Variable: is_genuine No. Observations: 1500
Model: Logit Df Residuals: 1495
Method: MLE Df Model: 4
Date: Thu, 25 Jul 2024 Pseudo R-squ.: 0.9543
Time: 19:54:59 Log-Likelihood: -43.646
converged: True LL-Null: -954.77
Covariance Type: nonrobust LLR p-value: 0.000
coef std err z P>|z| [0.025 0.975]
Intercept -321.8686 139.627 -2.305 0.021 -595.533 -48.204
height_right -2.8257 1.080 -2.617 0.009 -4.942 -0.709
length 6.0078 0.831 7.229 0.000 4.379 7.637
margin_up -10.1899 2.077 -4.906 0.000 -14.260 -6.119
margin_low -6.0361 0.903 -6.687 0.000 -7.805 -4.267


Possibly complete quasi-separation: A fraction 0.51 of observations can be
perfectly predicted. This might indicate that there is complete
quasi-separation. In this case some parameters will not be identified.

Performance du modèle Logit() de Statsmodels

In [36]:
DF_RLog = DF_F.copy()
#Columns = ['diagonal','height_left', 'height_right', 'margin_low', 'margin_up', 'length']
Columns = ['height_right', 'margin_low', 'margin_up', 'length']
# On affecte à nos X et y les données nécessaires
X = DF_RLog[Columns]
# Ajout d'une colonne constante pour l'intercept
X = sm.add_constant(X)
y = DF_RLog["is_genuine"]
# On créer notre train set et test set
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size = 0.2, random_state=random_state)

#régression logistique
Res_Log = Logit(endog=y_train,exog=X_train).fit()
display(Res_Log.summary())

# Effectuer des prédictions sur les données de test
y_pred = Res_Log.predict(X_test)
# Convertir les probabilités prédites en classes (0 ou 1)
y_pred_class = (y_pred > 0.5).astype(int)  # Choix du seuil de 0.5 pour la classification binaire

# Calculer les métriques
accuracy = round(accuracy_score(y_test, y_pred_class),4)
precision = round(precision_score(y_test, y_pred_class),4)
recall = round(recall_score(y_test, y_pred_class),4)
f1 = round(f1_score(y_test, y_pred_class),6)

Text_message("Accuracy : {}".format(accuracy))
Text_message("Précision : {}".format(precision))
Text_message("Recall : {}".format(recall))
Text_message("Le score f1 du modèle sur les données Test est de : {}%".format(f1*100))
Optimization terminated successfully.
         Current function value: 0.031837
         Iterations 12
Logit Regression Results
Dep. Variable: is_genuine No. Observations: 1200
Model: Logit Df Residuals: 1195
Method: MLE Df Model: 4
Date: Thu, 25 Jul 2024 Pseudo R-squ.: 0.9501
Time: 19:55:03 Log-Likelihood: -38.204
converged: True LL-Null: -765.20
Covariance Type: nonrobust LLR p-value: 0.000
coef std err z P>|z| [0.025 0.975]
const -278.9557 157.068 -1.776 0.076 -586.804 28.892
height_right -2.6995 1.222 -2.208 0.027 -5.095 -0.304
margin_low -5.9038 1.008 -5.854 0.000 -7.880 -3.927
margin_up -9.6090 2.126 -4.521 0.000 -13.775 -5.443
length 5.4885 0.811 6.767 0.000 3.899 7.078


Possibly complete quasi-separation: A fraction 0.48 of observations can be
perfectly predicted. This might indicate that there is complete
quasi-separation. In this case some parameters will not be identified.
Accuracy : 0.9933
Précision : 0.9902
Recall : 1.0
Le score f1 du modèle sur les données Test est de : 99.5074%

Performance du modèle LogisticRegression() de Sklearn

In [37]:
from sklearn.metrics import r2_score
# On affecte à nos X et y les données nécessaires en retirant les variables descriptives non significatives
X = DF_F.drop(["is_genuine","diagonal","height_left"], axis=1)
y = DF_F["is_genuine"]
# On définit notre Train Set et notre Test Set
X_train ,X_test, y_train, y_test = train_test_split(X,y, test_size=0.2, random_state=random_state)

# On définit notre model
model_LR = LogisticRegression()
# On entraine notre model
model_RL = model_LR.fit(X_train, y_train)
# On enregistre les prédictions dans une variable y_pred
y_pred = model_RL.predict(X_test)

# On vérifie le score de notre model sur les données Test (F1 score)
accuracy = round(accuracy_score(y_test, y_pred),4)
precision = round(precision_score(y_test, y_pred),4)
recall = round(recall_score(y_test, y_pred),4)
f1 = round(f1_score(y_test, y_pred),6)
Text_message("Accuracy : {}".format(accuracy))
Text_message("Précision : {}".format(precision))
Text_message("Recall : {}".format(recall))
Text_message("Le score f1 du modèle sur les données Test est de : {}%".format(f1*100))
# Calcul du R²
r2 = r2_score(y_test, y_pred)
Text_message("Le coefficient de détermination R² est : {}".format(r2.round(4)))
Accuracy : 0.9933
Précision : 0.9902
Recall : 1.0
Le score f1 du modèle sur les données Test est de : 99.5074%
Le coefficient de détermination R² est : 0.9697
6.2 - Choix de la fonction de Régression Logistique
In [38]:
DF_F1 = DF_F.copy()
In [39]:
Seuil = 0.5
test_size = 0.2
V_pred = ["height_right", "margin_low", "margin_up", "length"]
# Utilisation de la fonction de comparaison Statsmodels/SKlearn
# Mat_RegLog_Seuil(DF_F1, Seuil = Seuil, Set=['X_test'], test_size=test_size, V_pred = V_pred, random_state = random_state)
Mat_RegLog_Seuil(DF_F1, Seuil = Seuil, test_size=test_size, V_pred = V_pred, random_state = random_state)
Régression logistique avec Statsmodels
Optimization terminated successfully.
         Current function value: 0.031837
         Iterations 12
Billets considérés comme Vrais: 204 - Billets considérés comme Faux : 96
Après avoir croiser les données réelles de celles prédites, on observe :
Faux billets (classe 0): il existe 96 faux billets et 2 faux billets identifiés comme vrais
Vrais billets (classe 1): il existe 202 vrais billets et 0 vrais billets identifiés comme faux
No description has been provided for this image
Métriques d'évaluation
Accuracy: 0.9933
Précision: 0.9902
Recall: 1.0
F1_score: 99.5074 %
--------------------------------------------------------------------
Régression logistique avec Sklearn

Billets considérés comme Vrais: 204 - Billets considérés comme Faux : 96
Après avoir croiser les données réelles de celles prédites, on observe :
Faux billets (classe 0): il existe 96 faux billets et 2 faux billets identifiés comme vrais
Vrais billets (classe 1): il existe 202 vrais billets et 0 vrais billets identifiés comme faux
No description has been provided for this image
Métriques d'évaluation
L'accuracy : 0.9933
Précision : 0.9902
Recall : 1.0
F1_score : 99.5074 %

Les 2 modèles ont les mêmes performances

Appliquons les métriques sur le modèle Sklearn

In [40]:
Seuil = 0.5
test_size = 0.2
# On affecte à nos X et y les données nécessaires en retirant les variables descriptives non significatives
X = DF_F.drop(["is_genuine","diagonal","height_left"], axis=1)
# Centrage et réduction
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)
# Création d'un DF: 
X_scaled = pd.DataFrame(X_scaled, columns=X.columns)
y = DF_F["is_genuine"]
# On définit notre Train Set et notre Test Set
X_train ,X_test, y_train, y_test = train_test_split(X_scaled,y, test_size=test_size, random_state=random_state)
# On définit notre model
model_LR = LogisticRegression()
# On entraine notre model
model_LR.fit(X_train, y_train)


y_hat_proba = model_LR.predict_proba(X_test)[:,1]
y_Seuil = [ 0 if value < Seuil else 1 for value in y_hat_proba ]
# Matrice de confusion 
cm = confusion_matrix(y_test, y_Seuil)
true_negative, false_positive, false_negative, true_positive = cm.ravel()

plt.figure(figsize=(12, 4))
sns.heatmap(cm, annot = True, fmt = ".3g", cmap = sns.color_palette("rocket", as_cmap=True), 
            linecolor = "white", linewidths = 0.3, xticklabels = ["Faux","Vrai"], yticklabels=["0","1"])
plt.xlabel("Prédictions")
plt.ylabel("Réels")
plt.title("Matrice de confusion de la regression logistique Sklearn\navec un seuil = {}".format(round(Seuil,3)), 
          color='firebrick')
plt.show()

# Métriques de performance:
Tmessage("Métriques de performance")
F1_RLg = f1_score(y_test, y_Seuil)
Accu_Rlg = round(accuracy_score(y_test, y_Seuil), 4)
#Accu_Rlg = accuracy_score(y_test, y_Seuil).round(4)
Prec_Rlg = precision_score(y_test, y_Seuil).round(4)
Recall_Rlg = recall_score(y_test, y_Seuil).round(4)
Tmessage("F1_score sur le set Test: {} %".format(round(F1_RLg*100,4)), Color='black', Align='left')
Text_message("Nombre de prédictions correctes sur le nombre total d'échantillons - Accuracy : {}".format(Accu_Rlg))
Text_message("Précision des prédictions positives - Precision: {}".format(Prec_Rlg))
Text_message("Capacité du modèle à capturer tous les exemples positifs - Recall: {}".format(Recall_Rlg))

# Analyse des performances du modèle de classification => l'histogramme des probabilités des prédictions
# Obtenir les probabilités prédites pour la classe positive
y_hat_proba = model_LR.predict_proba(X_test)[:,1]

# Définir les pas de l'abscisse à 0.05
bins = np.arange(0, 1.05, 0.02)  
sns.set(rc={'axes.facecolor': 'white', 'figure.facecolor': 'white'})
plt.figure(figsize=(12,4))
plt.title('Probabilités des prédictions', y=1, 
          fontdict={'size': 12, 'weight': 'bold', 'style': 'italic', 'color': 'firebrick'})
# Afficher l'histogramme des probabilités prédites
sns.histplot(y_hat_proba, bins=bins, kde=True)  # kde=False pour supprimer l'estimation de densité
plt.xlabel('Probabilités prédites')
plt.ylabel('Fréquence')
plt.show()

# Adaptation du DF:
df_Taille_Adapt = DF_F1.dropna()

# Courbe R.O.C.:
fpr, tpr, _ = roc_curve(df_Taille_Adapt["is_genuine"],df_Taille_Adapt["proba"])
roc_auc = roc_auc_score(df_Taille_Adapt["is_genuine"],df_Taille_Adapt["proba"])
# Graphique:
Tmessage("Courbe R.O.C.", Size=16)
plt.figure(figsize=(10, 4))
plt.plot(fpr, tpr, color="firebrick", linewidth=2.5, label="aire sous la courbe = %0.2f" % roc_auc)
plt.plot([0, 1], [0, 1],color="grey", linestyle=":")
plt.xlim([0, 1])
plt.ylim([0, 1])
plt.legend(loc = "lower right")
plt.ylabel("Taux de vrais positifs")
plt.xlabel("Taux de faux positifs")
plt.grid(True, linestyle='--', color='gray', linewidth=0.5)
plt.axis('on')
plt.show()
Tmessage("Capacité du modèle à discriminer entre les classes - ROC-AUC: {}".format(round(roc_auc,6)), Color ='black',
        Align='left')

# Utilisation de la fonction learning_curve pour générer les données de courbe d'apprentissage
# nombre de splits pour la validation croisée (cv)
cv = 10
train_sizes, train_scores, test_scores = learning_curve(
    model_RL, X_train, y_train, cv=cv, train_sizes=np.linspace(0.1, 1.0, 10), scoring=make_scorer(accuracy_score))

# Calcul des scores moyens et des écart-types pour l'ensemble d'entraînement et l'ensemble de test
train_scores_mean = np.mean(train_scores, axis=1)
train_scores_std = np.std(train_scores, axis=1)
test_scores_mean = np.mean(test_scores, axis=1)
test_scores_std = np.std(test_scores, axis=1)

# Affichage des courbes d'apprentissage
Tmessage("Courbes d'apprentissage ({} splits)".format(cv), Size=16)
plt.figure(figsize=(10, 4))
plt.fill_between(train_sizes, train_scores_mean - train_scores_std,
                 train_scores_mean + train_scores_std, alpha=0.1, color="b")
plt.fill_between(train_sizes, test_scores_mean - test_scores_std,
                 test_scores_mean + test_scores_std, alpha=0.1, color="r")
plt.plot(train_sizes, train_scores_mean, 'o-', color="b", label="Score d'entraînement")
plt.plot(train_sizes, test_scores_mean, 'o-', color="r", label="Score de validation")

plt.xlabel("Taille de l'ensemble d'entraînement")
plt.ylabel("Score")
plt.legend(loc="best")
plt.show()
No description has been provided for this image
Métriques de performance
F1_score sur le set Test: 99.5074 %
Nombre de prédictions correctes sur le nombre total d'échantillons - Accuracy : 0.9933
Précision des prédictions positives - Precision: 0.9902
Capacité du modèle à capturer tous les exemples positifs - Recall: 1.0
No description has been provided for this image
Courbe R.O.C.
No description has been provided for this image
Capacité du modèle à discriminer entre les classes - ROC-AUC: 0.999798
Courbes d'apprentissage (10 splits)
No description has been provided for this image
In [41]:
# Enregistrement du fichier de référence:
DF_F.to_csv('DF_billets_reference.csv', index=False)  # Sauvegarde du DataFrame 'df' en fichier CSV 
7 - Algorythme / Fonction de détection de faux billets
7.1 - Choix de l'algorithme pour l'outil d'identification des billets
In [42]:
Tmessage("Algorithme de régression logistique", Size=16)
# Affichage des résultats

Tmessage("F1_score sur le set Test: {} %".format(round(F1_RLg*100,4)), Color='teal', Align='left')
Tmessage("Nombre de prédictions correctes sur le nombre total d'échantillons - Accuracy : {}".format(Accu_Rlg), 
         Color='black', Align='left', Size = 12)
Tmessage("Précision des prédictions positives - Precision: {}".format(Prec_Rlg), Color='black', Align='left', Size = 12)
Tmessage("Capacité du modèle à capturer tous les exemples positifs - Recall: {}".format(Recall_Rlg), 
         Color='black', Align='left', Size = 12)
display(model_RL)

Tmessage("Algorithme K_means", Size=16)
# Affichage des résultats
Tmessage("F1-score sur l'ensemble de test: {} %".format(round(F1_Kmeans*100,4)), Color='teal', Align='left')
Tmessage("Précision sur l'ensemble de test: {}".format(round((test_precision_Kmeans),4)), Color='black', 
         Align='left', Size = 12)
Tmessage("Rappel sur l'ensemble de test: {}".format(round((test_recall_Kmeans),4)), Color='black', Align='left', Size = 12)
model_Kmeans
Algorithme de régression logistique
F1_score sur le set Test: 99.5074 %
Nombre de prédictions correctes sur le nombre total d'échantillons - Accuracy : 0.9933
Précision des prédictions positives - Precision: 0.9902
Capacité du modèle à capturer tous les exemples positifs - Recall: 1.0
LogisticRegression()
In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook.
On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.
LogisticRegression()
Algorithme K_means
F1-score sur l'ensemble de test: 98.4432 %
Précision sur l'ensemble de test: 0.9844
Rappel sur l'ensemble de test: 0.9844
Out[42]:
KMeans(n_clusters=2)
In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook.
On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.
KMeans(n_clusters=2)

Avec un meilleur score, la régression logistique sera ulisée pour identifier les billets

7.2 - Création de fichiers Tests

Création d'un fichier Test

In [43]:
# ----------------------------------- Création d'un fichier test à partir de DF_F ---------------------------------------------
import random
# Création d'un DF de simulation
# Filtrer les lignes où is_genuine = 0 et is_genuine = 1
df_is_genuine_0 = DF_F[DF_F['is_genuine'] == 0]
df_is_genuine_1 = DF_F[DF_F['is_genuine'] == 1]

Pourcentage = 0.4
Nbr_Billets = 20
random_state1 = random.randint(0, 1000)
# Sélectionner aléatoirement des lignes où is_genuine = 0
random_0 = df_is_genuine_0.sample(frac=Pourcentage, random_state=random_state1)
# Sélectionner aléatoirement des lignes où is_genuine = 1
random_1 = df_is_genuine_1.sample(frac=1-Pourcentage, random_state=random_state1)

# Concaténer les deux sélections pour obtenir un DataFrame final avec 50 lignes
random_rows = pd.concat([random_0, random_1])

# Sélectionner aléatoirement 50 lignes parmi les 50 obtenues
DF_Test = random_rows.sample(n=Nbr_Billets, random_state=random_state)
DF_Test = DF_Test.reset_index()
DF_Test = DF_Test.drop("index", axis=1)

# Ajout de lignes aberrantes
data_to_append = [
    {'is_genuine': 0, "diagonal": 172, "height_left": 0, "height_right": 103.59, "margin_low": 4.080, "margin_up": 0, "length": 114.09},
    {'is_genuine': 0, "diagonal": 172.00, "height_left": 104.35, "height_right": 103.29, "margin_low": 3.730, "margin_up": 3.11, "length": 999.18},
    {'is_genuine': 0, "diagonal": 172.58, "height_left": 103.99, "height_right": 150, "margin_low": 4.430, "margin_up": 3.07, "length": 112.79},
    {'is_genuine': 0, "diagonal": 171.85, "height_left": 103.93, "height_right": 103.81, "margin_low": 4, "margin_up": 3.12, "length": 112.8},
    {'is_genuine': 0, "diagonal": 500.85, "height_left": 103.93, "height_right": 103.81, "margin_low": 0, "margin_up": 3.12, "length": 112.8},
    {'is_genuine': 0, "diagonal": 171.85, "height_left": 20, "height_right": 103.81, "margin_low": 4, "margin_up": 30.12, "length": 112.8},
    {'is_genuine': 0, "diagonal": 171.85, "height_left": 103.93, "height_right": 103.81, "margin_low": 40, "margin_up": 3.12, "length": 112.8},
    {'is_genuine': 0, "diagonal": 800, "height_left": 500, "height_right": 300, "margin_low": 40, "margin_up": 50, "length": 800},
    {'is_genuine': 0, "diagonal": 70, "height_left": 100, "height_right": 100, "margin_low": 7, "margin_up": 4, "length": 100},
]

# Créer un DataFrame à partir des données à ajouter
Data_sup = pd.DataFrame(data_to_append)
# Ajouter les lignes au DataFrame existant
DF_Test = pd.concat([DF_Test, Data_sup], ignore_index=True)

# Ajout d'une colonne 'Id'
n = len(DF_Test)
DF_Test.insert(0, 'Id', [f'B-{i}' for i in range(1, n+1)])
#Tmessage("Fichier test")
#display(DF_Test.head())
#display(DF_Test.shape)
DF_Verif = DF_Test.copy()
DF_Test1 = DF_Test.copy()

DF_T = pd.read_csv('Fichier_test_Mentor.csv', sep=";")
DF_T = DF_T[["diagonal", "height_left", "height_right", "margin_low", "margin_up", "length"]]

DF_Test1 = DF_Test1.drop(['is_genuine','Id'], axis=1)
DF_Test1 = pd.concat([DF_Test1, DF_T])
DF_Test1 = DF_Test1.reset_index(drop=True)
# Ajout d'une colonne 'Id'
n = len(DF_Test1)
DF_Test1.insert(0, 'Id', [f'B-{i}' for i in range(1, n+1)])
7.3 - Appel de la fonction "detection_faux_billets()" et vérification des fichiers

Avec Statsmodels

In [44]:
DF_T = pd.read_csv('billets_testN.csv')
detection_faux_billets_SM(DF_T)
Optimization terminated successfully.
         Current function value: 0.029939
         Iterations 11
--------------------------------------------------
Outil de détection de faux billets
--------------------------------------------------
Aperçue du fichier test
diagonal height_left height_right margin_low margin_up length id
0 172.09 103.95 103.73 4.39 3.09 113.19 B_1
1 171.52 104.17 104.03 5.27 3.16 111.82 B_2
2 171.78 103.80 103.75 3.81 3.24 113.39 B_3
3 172.02 104.08 103.99 5.57 3.30 111.10 B_4
4 171.79 104.34 104.37 5.00 3.07 111.87 B_5
On ne conserve que les colonnes prédictives
diagonal height_left height_right margin_low margin_up length
0 172.09 103.95 103.73 4.39 3.09 113.19
1 171.52 104.17 104.03 5.27 3.16 111.82
2 171.78 103.80 103.75 3.81 3.24 113.39
Le fichier test contient 5 billets à vérifier
--------------------------------------------------
Suppression des lignes contenant des valeurs non autorisées
le fichiers ne contient aucune valeur nulle
le fichiers ne contient aucune valeur manquante
le fichiers ne contient aucune valeur texte
--------------------------------------------------
Vérification présence de dimensions abérrantes
- 'diagonal' est Ok
- 'height_left' est Ok
- 'height_right' est Ok
- 'margin_low' est Ok
- 'margin_up' est Ok
- 'length' est Ok
Toutes les lignes aberrantes ont été supprimées du dataset
Le dataset contient 5 lignes avant détection des vrais billets
--------------------------------------------------
Imputation de 'is_genuine' suivant le modèle
  is_genuine_Predict % Probablement VRAI diagonal height_left height_right margin_low margin_up length id
0 1 99.930000 172.090000 103.950000 103.730000 4.390000 3.090000 113.190000 B_1
1 0 0.050000 171.520000 104.170000 104.030000 5.270000 3.160000 111.820000 B_2
2 1 100.000000 171.780000 103.800000 103.750000 3.810000 3.240000 113.390000 B_3
3 0 0.000000 172.020000 104.080000 103.990000 5.570000 3.300000 111.100000 B_4
4 0 0.140000 171.790000 104.340000 104.370000 5.000000 3.070000 111.870000 B_5
--------------------------------------------------
Contenu du fichier test : 2 Vrais billets et 3 Faux billets
--------------------------------------------------
Lignes du fichier mises à l'écart
Aucune ligne n'a été mise de coté

Avec Sklearn

In [45]:
DF_T = pd.read_csv('billets_testN.csv')
detection_faux_billets_SK(DF_T)
--------------------------------------------------
Outil de détection de faux billets
--------------------------------------------------
Aperçue du fichier test
diagonal height_left height_right margin_low margin_up length id
0 172.09 103.95 103.73 4.39 3.09 113.19 B_1
1 171.52 104.17 104.03 5.27 3.16 111.82 B_2
2 171.78 103.80 103.75 3.81 3.24 113.39 B_3
3 172.02 104.08 103.99 5.57 3.30 111.10 B_4
4 171.79 104.34 104.37 5.00 3.07 111.87 B_5
On ne conserve que les colonnes prédictives
diagonal height_left height_right margin_low margin_up length
0 172.09 103.95 103.73 4.39 3.09 113.19
1 171.52 104.17 104.03 5.27 3.16 111.82
2 171.78 103.80 103.75 3.81 3.24 113.39
Le fichier test contient 5 billets à vérifier
--------------------------------------------------
Suppression des lignes contenant des valeurs non autorisées
le fichiers ne contient aucune valeur nulle
le fichiers ne contient aucune valeur manquante
le fichiers ne contient aucune valeur texte
--------------------------------------------------
Vérification présence de dimensions abérrantes
- 'diagonal' est Ok
- 'height_left' est Ok
- 'height_right' est Ok
- 'margin_low' est Ok
- 'margin_up' est Ok
- 'length' est Ok
Toutes les lignes aberrantes ont été supprimées du dataset
Le dataset contient 5 lignes avant détection des vrais billets
--------------------------------------------------
Imputation de 'is_genuine' suivant le modèle
  is_genuine_Predict % Probablement VRAI diagonal height_left height_right margin_low margin_up length id
0 1 98.950000 172.090000 103.950000 103.730000 4.390000 3.090000 113.190000 B_1
1 0 1.340000 171.520000 104.170000 104.030000 5.270000 3.160000 111.820000 B_2
2 1 99.890000 171.780000 103.800000 103.750000 3.810000 3.240000 113.390000 B_3
3 0 0.030000 172.020000 104.080000 103.990000 5.570000 3.300000 111.100000 B_4
4 0 1.740000 171.790000 104.340000 104.370000 5.000000 3.070000 111.870000 B_5
--------------------------------------------------
Contenu du fichier test : 2 Vrais billets et 3 Faux billets
--------------------------------------------------
Lignes du fichier mises à l'écart
Aucune ligne n'a été mise de coté

Test avec un fichier non conforme

In [46]:
DF_T = pd.read_csv('Fichier_test_Mentor.csv', sep=";")
detection_faux_billets_SM(DF_T)
Optimization terminated successfully.
         Current function value: 0.029939
         Iterations 11
--------------------------------------------------
Outil de détection de faux billets
--------------------------------------------------
Aperçue du fichier test
diagonal height_left height_right margin_low margin_up length id
0 0 0 0.00 0.00 0.00 0.00 B_1
1 Az R 104.03 5.27 3.16 111.82 B_2
2 100 50 103.75 3.81 3.24 113.39 B_3
3 0.0001 0.0001 103.99 5.57 3.30 111.10 B_4
4 999 999 104.37 5.00 3.07 111.87 B_5
On ne conserve que les colonnes prédictives
diagonal height_left height_right margin_low margin_up length
0 0 0 0.00 0.00 0.00 0.00
1 Az R 104.03 5.27 3.16 111.82
2 100 50 103.75 3.81 3.24 113.39
Le fichier test contient 7 billets à vérifier
--------------------------------------------------
Suppression des lignes contenant des valeurs non autorisées
DF des enregistrements contenant des 0
diagonal height_left height_right margin_low margin_up length
0 0 0 0.0 0.0 0.0 0.0
DF des enregistrements contenant des valeurs manquantes
diagonal height_left height_right margin_low margin_up length
6 NaN NaN 104.37 5.0 3.07 111.87
DF des enregistrements contenant du texte
diagonal height_left height_right margin_low margin_up length
1 Az R 104.03 5.27 3.16 111.82
--------------------------------------------------
Vérification présence de dimensions abérrantes
- Mesure(s) non compatible(s) dans 'diagonal'
diagonal height_left height_right margin_low margin_up length
2 100.0000 50.0000 103.75 3.81 3.24 113.39
3 0.0001 0.0001 103.99 5.57 3.30 111.10
4 999.0000 999.0000 104.37 5.00 3.07 111.87
5 345.0000 435.0000 104.37 5.00 3.07 111.87
- Mesure(s) non compatible(s) dans 'height_left'
diagonal height_left height_right margin_low margin_up length
2 100.0000 50.0000 103.75 3.81 3.24 113.39
3 0.0001 0.0001 103.99 5.57 3.30 111.10
4 999.0000 999.0000 104.37 5.00 3.07 111.87
5 345.0000 435.0000 104.37 5.00 3.07 111.87
- 'height_right' est Ok
- 'margin_low' est Ok
- 'margin_up' est Ok
- 'length' est Ok
Le dataset ne contient plus aucun billet à vérifier

Test avec le fichier généré

In [47]:
detection_faux_billets_SM(DF_Test)
Optimization terminated successfully.
         Current function value: 0.029939
         Iterations 11
--------------------------------------------------
Outil de détection de faux billets
--------------------------------------------------
Aperçue du fichier test
Id is_genuine diagonal height_left height_right margin_low margin_up length
0 B-1 1 172.63 104.13 104.17 3.77 3.45 113.34
1 B-2 0 172.40 104.55 104.22 5.18 3.51 111.94
2 B-3 0 171.91 103.99 104.23 5.01 3.42 111.77
3 B-4 0 171.21 104.29 104.41 5.14 3.26 111.16
4 B-5 1 171.87 104.29 103.53 3.91 2.89 112.91
On ne conserve que les colonnes prédictives
diagonal height_left height_right margin_low margin_up length
0 172.63 104.13 104.17 3.77 3.45 113.34
1 172.40 104.55 104.22 5.18 3.51 111.94
2 171.91 103.99 104.23 5.01 3.42 111.77
Le fichier test contient 29 billets à vérifier
--------------------------------------------------
Suppression des lignes contenant des valeurs non autorisées
DF des enregistrements contenant des 0
diagonal height_left height_right margin_low margin_up length
20 172.00 0.00 103.59 4.08 0.00 114.09
24 500.85 103.93 103.81 0.00 3.12 112.80
le fichiers ne contient aucune valeur manquante
le fichiers ne contient aucune valeur texte
--------------------------------------------------
Vérification présence de dimensions abérrantes
- Mesure(s) non compatible(s) dans 'diagonal'
diagonal height_left height_right margin_low margin_up length
27 800.0 500.0 300.0 40.0 50.0 800.0
28 70.0 100.0 100.0 7.0 4.0 100.0
- Mesure(s) non compatible(s) dans 'height_left'
diagonal height_left height_right margin_low margin_up length
25 171.85 20.0 103.81 4.0 30.12 112.8
27 800.00 500.0 300.00 40.0 50.00 800.0
28 70.00 100.0 100.00 7.0 4.00 100.0
- Mesure(s) non compatible(s) dans 'height_right'
diagonal height_left height_right margin_low margin_up length
22 172.58 103.99 150.0 4.43 3.07 112.79
27 800.00 500.00 300.0 40.00 50.00 800.00
28 70.00 100.00 100.0 7.00 4.00 100.00
- Mesure(s) non compatible(s) dans 'margin_low'
diagonal height_left height_right margin_low margin_up length
26 171.85 103.93 103.81 40.0 3.12 112.8
27 800.00 500.00 300.00 40.0 50.00 800.0
28 70.00 100.00 100.00 7.0 4.00 100.0
- Mesure(s) non compatible(s) dans 'margin_up'
diagonal height_left height_right margin_low margin_up length
25 171.85 20.0 103.81 4.0 30.12 112.8
27 800.00 500.0 300.00 40.0 50.00 800.0
28 70.00 100.0 100.00 7.0 4.00 100.0
- Mesure(s) non compatible(s) dans 'length'
diagonal height_left height_right margin_low margin_up length
21 172.0 104.35 103.29 3.73 3.11 999.18
27 800.0 500.00 300.00 40.00 50.00 800.00
28 70.0 100.00 100.00 7.00 4.00 100.00
Toutes les lignes aberrantes ont été supprimées du dataset
Le dataset contient 21 lignes avant détection des vrais billets
--------------------------------------------------
Imputation de 'is_genuine' suivant le modèle
Vérification du modèle avec le dataset de test
  is_genuine_Predict % Probablement VRAI Id is_genuine diagonal height_left height_right margin_low margin_up length
0 1 99.840000 B-1 1 172.630000 104.130000 104.170000 3.770000 3.450000 113.340000
1 0 0.000000 B-2 0 172.400000 104.550000 104.220000 5.180000 3.510000 111.940000
2 0 0.010000 B-3 0 171.910000 103.990000 104.230000 5.010000 3.420000 111.770000
3 0 0.000000 B-4 0 171.210000 104.290000 104.410000 5.140000 3.260000 111.160000
4 1 100.000000 B-5 1 171.870000 104.290000 103.530000 3.910000 2.890000 112.910000
5 1 99.260000 B-6 1 171.360000 103.720000 104.760000 4.170000 2.880000 113.140000
6 0 0.010000 B-7 0 172.310000 104.310000 104.720000 4.860000 3.410000 112.060000
7 1 99.990000 B-8 1 171.790000 103.510000 103.250000 4.050000 3.080000 112.710000
8 1 99.990000 B-9 1 172.520000 103.900000 104.030000 3.970000 2.980000 113.300000
9 1 100.000000 B-10 1 171.870000 103.940000 103.220000 3.710000 3.090000 113.190000
10 0 0.000000 B-11 0 171.960000 104.450000 104.480000 5.310000 3.420000 111.750000
11 1 99.920000 B-12 1 172.020000 104.070000 104.130000 3.720000 2.960000 112.560000
12 1 100.000000 B-13 1 171.650000 103.880000 103.590000 3.940000 3.050000 113.280000
13 0 0.010000 B-14 0 172.410000 104.320000 103.930000 5.820000 3.360000 112.360000
14 0 0.000000 B-15 0 172.050000 104.220000 104.050000 5.870000 3.180000 111.130000
15 1 99.910000 B-16 1 171.410000 104.030000 104.210000 4.260000 3.110000 113.490000
16 1 99.340000 B-17 1 171.770000 103.920000 103.900000 4.610000 2.950000 112.950000
17 1 98.090000 B-18 1 171.950000 103.440000 103.800000 4.080000 3.190000 112.430000
18 1 99.990000 B-19 1 172.280000 103.630000 103.970000 3.790000 2.940000 112.900000
19 1 100.000000 B-20 1 172.110000 104.120000 103.830000 3.900000 2.720000 113.280000
23 1 99.910000 B-24 0 171.850000 103.930000 103.810000 4.000000 3.120000 112.800000
--------------------------------------------------
Contenu du fichier test : 14 Vrais billets et 7 Faux billets
--------------------------------------------------
Lignes du fichier mises à l'écart
Présence de valeurs nulles : 2 billets concernés.
diagonal height_left height_right margin_low margin_up length Id
20 172.00 0.00 103.59 4.08 0.00 114.09 B-21
24 500.85 103.93 103.81 0.00 3.12 112.80 B-25
Présence de texte : 0 billets concernés.
diagonal height_left height_right margin_low margin_up length Id
Présence de valeurs manquantes (NaN) : 0 billets concernés.
diagonal height_left height_right margin_low margin_up length Id
Dimensions hors gabarit : 6 billets concernés.
diagonal height_left height_right margin_low margin_up length Id
0 800.00 500.00 300.00 40.00 50.00 800.00 B-1
1 70.00 100.00 100.00 7.00 4.00 100.00 B-2
2 171.85 20.00 103.81 4.00 30.12 112.80 B-3
5 172.58 103.99 150.00 4.43 3.07 112.79 B-6
8 171.85 103.93 103.81 40.00 3.12 112.80 B-9
14 172.00 104.35 103.29 3.73 3.11 999.18 B-15

Test avec le fichier généré + fichier non conforme

In [48]:
detection_faux_billets_SM(DF_Test1)
Optimization terminated successfully.
         Current function value: 0.029939
         Iterations 11
--------------------------------------------------
Outil de détection de faux billets
--------------------------------------------------
Aperçue du fichier test
Id diagonal height_left height_right margin_low margin_up length
0 B-1 172.63 104.13 104.17 3.77 3.45 113.34
1 B-2 172.4 104.55 104.22 5.18 3.51 111.94
2 B-3 171.91 103.99 104.23 5.01 3.42 111.77
3 B-4 171.21 104.29 104.41 5.14 3.26 111.16
4 B-5 171.87 104.29 103.53 3.91 2.89 112.91
On ne conserve que les colonnes prédictives
diagonal height_left height_right margin_low margin_up length
0 172.63 104.13 104.17 3.77 3.45 113.34
1 172.4 104.55 104.22 5.18 3.51 111.94
2 171.91 103.99 104.23 5.01 3.42 111.77
Le fichier test contient 36 billets à vérifier
--------------------------------------------------
Suppression des lignes contenant des valeurs non autorisées
DF des enregistrements contenant des 0
diagonal height_left height_right margin_low margin_up length
20 172.0 0.0 103.59 4.08 0.00 114.09
24 500.85 103.93 103.81 0.00 3.12 112.80
29 0 0 0.00 0.00 0.00 0.00
DF des enregistrements contenant des valeurs manquantes
diagonal height_left height_right margin_low margin_up length
35 NaN NaN 104.37 5.0 3.07 111.87
DF des enregistrements contenant du texte
diagonal height_left height_right margin_low margin_up length
30 Az R 104.03 5.27 3.16 111.82
--------------------------------------------------
Vérification présence de dimensions abérrantes
- Mesure(s) non compatible(s) dans 'diagonal'
diagonal height_left height_right margin_low margin_up length
27 800.0000 500.0000 300.00 40.00 50.00 800.00
28 70.0000 100.0000 100.00 7.00 4.00 100.00
31 100.0000 50.0000 103.75 3.81 3.24 113.39
32 0.0001 0.0001 103.99 5.57 3.30 111.10
33 999.0000 999.0000 104.37 5.00 3.07 111.87
34 345.0000 435.0000 104.37 5.00 3.07 111.87
- Mesure(s) non compatible(s) dans 'height_left'
diagonal height_left height_right margin_low margin_up length
25 171.8500 20.0000 103.81 4.00 30.12 112.80
27 800.0000 500.0000 300.00 40.00 50.00 800.00
28 70.0000 100.0000 100.00 7.00 4.00 100.00
31 100.0000 50.0000 103.75 3.81 3.24 113.39
32 0.0001 0.0001 103.99 5.57 3.30 111.10
33 999.0000 999.0000 104.37 5.00 3.07 111.87
34 345.0000 435.0000 104.37 5.00 3.07 111.87
- Mesure(s) non compatible(s) dans 'height_right'
diagonal height_left height_right margin_low margin_up length
22 172.58 103.99 150.0 4.43 3.07 112.79
27 800.00 500.00 300.0 40.00 50.00 800.00
28 70.00 100.00 100.0 7.00 4.00 100.00
- Mesure(s) non compatible(s) dans 'margin_low'
diagonal height_left height_right margin_low margin_up length
26 171.85 103.93 103.81 40.0 3.12 112.8
27 800.00 500.00 300.00 40.0 50.00 800.0
28 70.00 100.00 100.00 7.0 4.00 100.0
- Mesure(s) non compatible(s) dans 'margin_up'
diagonal height_left height_right margin_low margin_up length
25 171.85 20.0 103.81 4.0 30.12 112.8
27 800.00 500.0 300.00 40.0 50.00 800.0
28 70.00 100.0 100.00 7.0 4.00 100.0
- Mesure(s) non compatible(s) dans 'length'
diagonal height_left height_right margin_low margin_up length
21 172.0 104.35 103.29 3.73 3.11 999.18
27 800.0 500.00 300.00 40.00 50.00 800.00
28 70.0 100.00 100.00 7.00 4.00 100.00
Toutes les lignes aberrantes ont été supprimées du dataset
Le dataset contient 21 lignes avant détection des vrais billets
--------------------------------------------------
Imputation de 'is_genuine' suivant le modèle
  is_genuine_Predict % Probablement VRAI Id diagonal height_left height_right margin_low margin_up length
0 1 99.840000 B-1 172.630000 104.130000 104.170000 3.770000 3.450000 113.340000
1 0 0.000000 B-2 172.400000 104.550000 104.220000 5.180000 3.510000 111.940000
2 0 0.010000 B-3 171.910000 103.990000 104.230000 5.010000 3.420000 111.770000
3 0 0.000000 B-4 171.210000 104.290000 104.410000 5.140000 3.260000 111.160000
4 1 100.000000 B-5 171.870000 104.290000 103.530000 3.910000 2.890000 112.910000
5 1 99.260000 B-6 171.360000 103.720000 104.760000 4.170000 2.880000 113.140000
6 0 0.010000 B-7 172.310000 104.310000 104.720000 4.860000 3.410000 112.060000
7 1 99.990000 B-8 171.790000 103.510000 103.250000 4.050000 3.080000 112.710000
8 1 99.990000 B-9 172.520000 103.900000 104.030000 3.970000 2.980000 113.300000
9 1 100.000000 B-10 171.870000 103.940000 103.220000 3.710000 3.090000 113.190000
10 0 0.000000 B-11 171.960000 104.450000 104.480000 5.310000 3.420000 111.750000
11 1 99.920000 B-12 172.020000 104.070000 104.130000 3.720000 2.960000 112.560000
12 1 100.000000 B-13 171.650000 103.880000 103.590000 3.940000 3.050000 113.280000
13 0 0.010000 B-14 172.410000 104.320000 103.930000 5.820000 3.360000 112.360000
14 0 0.000000 B-15 172.050000 104.220000 104.050000 5.870000 3.180000 111.130000
15 1 99.910000 B-16 171.410000 104.030000 104.210000 4.260000 3.110000 113.490000
16 1 99.340000 B-17 171.770000 103.920000 103.900000 4.610000 2.950000 112.950000
17 1 98.090000 B-18 171.950000 103.440000 103.800000 4.080000 3.190000 112.430000
18 1 99.990000 B-19 172.280000 103.630000 103.970000 3.790000 2.940000 112.900000
19 1 100.000000 B-20 172.110000 104.120000 103.830000 3.900000 2.720000 113.280000
23 1 99.910000 B-24 171.850000 103.930000 103.810000 4.000000 3.120000 112.800000
--------------------------------------------------
Contenu du fichier test : 14 Vrais billets et 7 Faux billets
--------------------------------------------------
Lignes du fichier mises à l'écart
Présence de valeurs nulles : 3 billets concernés.
diagonal height_left height_right margin_low margin_up length Id
20 172.0 0.0 103.59 4.08 0.00 114.09 B-21
24 500.85 103.93 103.81 0.00 3.12 112.80 B-25
29 0 0 0.00 0.00 0.00 0.00 B-30
Présence de texte : 1 billets concernés.
diagonal height_left height_right margin_low margin_up length Id
30 Az R 104.03 5.27 3.16 111.82 B-31
Présence de valeurs manquantes (NaN) : 1 billets concernés.
diagonal height_left height_right margin_low margin_up length Id
35 NaN NaN 104.37 5.0 3.07 111.87 B-36
Dimensions hors gabarit : 10 billets concernés.
diagonal height_left height_right margin_low margin_up length Id
0 800.0000 500.0000 300.00 40.00 50.00 800.00 B-1
1 70.0000 100.0000 100.00 7.00 4.00 100.00 B-2
2 100.0000 50.0000 103.75 3.81 3.24 113.39 B-3
3 0.0001 0.0001 103.99 5.57 3.30 111.10 B-4
4 999.0000 999.0000 104.37 5.00 3.07 111.87 B-5
5 345.0000 435.0000 104.37 5.00 3.07 111.87 B-6
6 171.8500 20.0000 103.81 4.00 30.12 112.80 B-7
13 172.5800 103.9900 150.00 4.43 3.07 112.79 B-14
16 171.8500 103.9300 103.81 40.00 3.12 112.80 B-17
22 172.0000 104.3500 103.29 3.73 3.11 999.18 B-23
8 - Outil de définition du seuil d’acceptation
In [49]:
# Statsmodels à 0 =  0.99903
# Sklearn à 0 =    0.9920505
Seuil = 0.8
test_size = 0.3
V_pred = ["height_right", "margin_low", "margin_up", "length"]
Acceptation_Seuil(DF_F, Seuil = Seuil, test_size = test_size, V_pred = V_pred)
Optimization terminated successfully.
         Current function value: 0.035116
         Iterations 11
Seuil d'acceptation Perte de vrais billets/ Refus de faux billets

No description has been provided for this image

Le modèle de régression logistique de Statsmodels obtient de meilleurs performances dans la détection de faux billets pour la plupart des seuils

In [ ]: